001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.server.core.partition.impl.btree.jdbm;
021    
022    
023    import java.io.File;
024    import java.util.ArrayList;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Set;
031    
032    import jdbm.RecordManager;
033    import jdbm.helper.MRU;
034    import jdbm.recman.BaseRecordManager;
035    import jdbm.recman.CacheRecordManager;
036    
037    import org.apache.directory.server.constants.ApacheSchemaConstants;
038    import org.apache.directory.server.core.entry.ClonedServerEntry;
039    import org.apache.directory.server.i18n.I18n;
040    import org.apache.directory.server.xdbm.Index;
041    import org.apache.directory.server.xdbm.IndexCursor;
042    import org.apache.directory.server.xdbm.IndexEntry;
043    import org.apache.directory.server.xdbm.IndexNotFoundException;
044    import org.apache.directory.server.xdbm.Store;
045    import org.apache.directory.shared.ldap.MultiException;
046    import org.apache.directory.shared.ldap.constants.SchemaConstants;
047    import org.apache.directory.shared.ldap.cursor.Cursor;
048    import org.apache.directory.shared.ldap.entry.EntryAttribute;
049    import org.apache.directory.shared.ldap.entry.Modification;
050    import org.apache.directory.shared.ldap.entry.ModificationOperation;
051    import org.apache.directory.shared.ldap.entry.ServerEntry;
052    import org.apache.directory.shared.ldap.entry.Value;
053    import org.apache.directory.shared.ldap.exception.LdapAliasDereferencingException;
054    import org.apache.directory.shared.ldap.exception.LdapAliasException;
055    import org.apache.directory.shared.ldap.exception.LdapException;
056    import org.apache.directory.shared.ldap.exception.LdapNoSuchObjectException;
057    import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
058    import org.apache.directory.shared.ldap.message.ResultCodeEnum;
059    import org.apache.directory.shared.ldap.name.AVA;
060    import org.apache.directory.shared.ldap.name.DN;
061    import org.apache.directory.shared.ldap.name.RDN;
062    import org.apache.directory.shared.ldap.schema.AttributeType;
063    import org.apache.directory.shared.ldap.schema.MatchingRule;
064    import org.apache.directory.shared.ldap.schema.SchemaManager;
065    import org.apache.directory.shared.ldap.util.NamespaceTools;
066    import org.slf4j.Logger;
067    import org.slf4j.LoggerFactory;
068    
069    
070    public class JdbmStore<E> implements Store<E, Long>
071    {
072        /** static logger */
073        private static final Logger LOG = LoggerFactory.getLogger( JdbmStore.class );
074    
075        /** The default cache size is set to 10 000 objects */
076        static final int DEFAULT_CACHE_SIZE = 10000;
077    
078        /** the JDBM record manager used by this database */
079        private RecordManager recMan;
080    
081        /** the normalized suffix DN of this backend database */
082        private DN normSuffix;
083    
084        /** the user provided suffix DN of this backend database */
085        private DN upSuffix;
086    
087        /** the working directory to use for files */
088        private File workingDirectory;
089    
090        /** the master table storing entries by primary key */
091        private JdbmMasterTable<ServerEntry> master;
092    
093        /** a map of attributeType numeric ID to user userIndices */
094        private Map<String, Index<?, E, Long>> userIndices = new HashMap<String, Index<?, E, Long>>();
095    
096        /** a map of attributeType numeric ID to system userIndices */
097        private Map<String, Index<?, E, Long>> systemIndices = new HashMap<String, Index<?, E, Long>>();
098    
099        /** true if initialized */
100        private boolean initialized;
101    
102        /** true if we sync disks on every write operation */
103        private boolean isSyncOnWrite = true;
104    
105        /** the normalized distinguished name index */
106        private JdbmIndex<String, E> ndnIdx;
107    
108        /** the user provided distinguished name index */
109        private JdbmIndex<String, E> updnIdx;
110    
111        /** the attribute presence index */
112        private JdbmIndex<String, E> presenceIdx;
113    
114        /** a system index on aliasedObjectName attribute */
115        private JdbmIndex<String, E> aliasIdx;
116    
117        /** a system index on the entries of descendants of root DN*/
118        private JdbmIndex<Long, E> subLevelIdx;
119    
120        /** the parent child relationship index */
121        private JdbmIndex<Long, E> oneLevelIdx;
122    
123        /** the one level scope alias index */
124        private JdbmIndex<Long, E> oneAliasIdx;
125    
126        /** the subtree scope alias index */
127        private JdbmIndex<Long, E> subAliasIdx;
128    
129        /** a system index on objectClass attribute*/
130        private JdbmIndex<String, E> objectClassIdx;
131    
132        /** a system index on entryCSN attribute */
133        private JdbmIndex<String, E> entryCsnIdx;
134    
135        /** a system index on entryUUID attribute */
136        private JdbmIndex<String, E> entryUuidIdx;
137    
138        /** Static declarations to avoid lookup all over the code */
139        private static AttributeType OBJECT_CLASS_AT;
140        private static AttributeType ENTRY_CSN_AT;
141        private static AttributeType ENTRY_UUID_AT;
142        private static AttributeType ALIASED_OBJECT_NAME_AT;
143    
144        /** A pointer on the schemaManager */
145        private SchemaManager schemaManager;
146    
147        private String suffixDn;
148        private int cacheSize = DEFAULT_CACHE_SIZE;
149        private String name;
150    
151    
152        // ------------------------------------------------------------------------
153        // C O N S T R U C T O R S
154        // ------------------------------------------------------------------------
155        /**
156         * Creates a store based on JDBM B+Trees.
157         */
158        public JdbmStore()
159        {
160        }
161    
162    
163        // -----------------------------------------------------------------------
164        // C O N F I G U R A T I O N   M E T H O D S
165        // -----------------------------------------------------------------------
166        private void protect( String property )
167        {
168            if ( initialized )
169            {
170                throw new IllegalStateException( I18n.err( I18n.ERR_576, property ) );
171            }
172        }
173    
174    
175        public void setWorkingDirectory( File workingDirectory )
176        {
177            protect( "workingDirectory" );
178            this.workingDirectory = workingDirectory;
179        }
180    
181    
182        public File getWorkingDirectory()
183        {
184            return workingDirectory;
185        }
186    
187    
188        public void setSuffixDn( String suffixDn )
189        {
190            protect( "suffixDn" );
191            this.suffixDn = suffixDn;
192        }
193    
194    
195        public String getSuffixDn()
196        {
197            return suffixDn;
198        }
199    
200    
201        public void setSyncOnWrite( boolean isSyncOnWrite )
202        {
203            protect( "syncOnWrite" );
204            this.isSyncOnWrite = isSyncOnWrite;
205        }
206    
207    
208        public boolean isSyncOnWrite()
209        {
210            return isSyncOnWrite;
211        }
212    
213    
214        public void setCacheSize( int cacheSize )
215        {
216            protect( "cacheSize" );
217            this.cacheSize = cacheSize;
218        }
219    
220    
221        public int getCacheSize()
222        {
223            return cacheSize;
224        }
225    
226    
227        public void setName( String name )
228        {
229            protect( "name" );
230            this.name = name;
231        }
232    
233    
234        public String getName()
235        {
236            return name;
237        }
238    
239    
240        // -----------------------------------------------------------------------
241        // E N D   C O N F I G U R A T I O N   M E T H O D S
242        // -----------------------------------------------------------------------
243    
244        public Long getDefaultId()
245        {
246            return 1L;
247        };
248    
249    
250        /**
251         * Initialize the JDBM storage system.
252         *
253         * @param schemaManager the schema manager
254         * @throws Exception on failure to lookup elements in schemaManager or create database files
255         */
256        public synchronized void init( SchemaManager schemaManager ) throws Exception
257        {
258            this.schemaManager = schemaManager;
259    
260            // Initialize Attribute types used all over this method
261            OBJECT_CLASS_AT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.OBJECT_CLASS_AT );
262            ALIASED_OBJECT_NAME_AT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.ALIASED_OBJECT_NAME_AT );
263            ENTRY_CSN_AT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.ENTRY_CSN_AT );
264            ENTRY_UUID_AT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.ENTRY_UUID_AT );
265    
266            this.upSuffix = new DN( suffixDn );
267            this.normSuffix = DN.normalize( upSuffix, schemaManager.getNormalizerMapping() );
268            workingDirectory.mkdirs();
269    
270            // First, check if the file storing the data exists
271            String path = workingDirectory.getPath() + File.separator + "master";
272            BaseRecordManager base = new BaseRecordManager( path );
273            base.disableTransactions();
274    
275            if ( cacheSize < 0 )
276            {
277                cacheSize = DEFAULT_CACHE_SIZE;
278                LOG.debug( "Using the default entry cache size of {} for {} partition", cacheSize, name );
279            }
280            else
281            {
282                LOG.debug( "Using the custom configured cache size of {} for {} partition", cacheSize, name );
283            }
284    
285            // Now, create the entry cache for this partition
286            recMan = new CacheRecordManager( base, new MRU( cacheSize ) );
287    
288            // Create the master table (the table containing all the entries)
289            master = new JdbmMasterTable<ServerEntry>( recMan, schemaManager );
290    
291            // -------------------------------------------------------------------
292            // Initializes the user and system indices
293            // -------------------------------------------------------------------
294    
295            setupSystemIndices();
296            setupUserIndices();
297    
298            // We are done !
299            initialized = true;
300        }
301    
302    
303        @SuppressWarnings("unchecked")
304        private void setupSystemIndices() throws Exception
305        {
306            if ( systemIndices.size() > 0 )
307            {
308                HashMap<String, Index<?, E, Long>> tmp = new HashMap<String, Index<?, E, Long>>();
309    
310                for ( Index<?, E, Long> index : systemIndices.values() )
311                {
312                    String oid = schemaManager.getAttributeTypeRegistry().getOidByName( index.getAttributeId() );
313                    tmp.put( oid, index );
314                    ( ( JdbmIndex ) index ).init( schemaManager, schemaManager.lookupAttributeTypeRegistry( oid ),
315                        workingDirectory );
316                }
317                systemIndices = tmp;
318            }
319    
320            if ( ndnIdx == null )
321            {
322                ndnIdx = new JdbmIndex<String, E>();
323                ndnIdx.setAttributeId( ApacheSchemaConstants.APACHE_N_DN_AT_OID );
324                systemIndices.put( ApacheSchemaConstants.APACHE_N_DN_AT_OID, ndnIdx );
325                ndnIdx.init( schemaManager, schemaManager
326                    .lookupAttributeTypeRegistry( ApacheSchemaConstants.APACHE_N_DN_AT_OID ), workingDirectory );
327            }
328    
329            if ( updnIdx == null )
330            {
331                updnIdx = new JdbmIndex<String, E>();
332                updnIdx.setAttributeId( ApacheSchemaConstants.APACHE_UP_DN_AT_OID );
333                systemIndices.put( ApacheSchemaConstants.APACHE_UP_DN_AT_OID, updnIdx );
334                updnIdx.init( schemaManager, schemaManager
335                    .lookupAttributeTypeRegistry( ApacheSchemaConstants.APACHE_UP_DN_AT_OID ), workingDirectory );
336            }
337    
338            if ( presenceIdx == null )
339            {
340                presenceIdx = new JdbmIndex<String, E>();
341                presenceIdx.setAttributeId( ApacheSchemaConstants.APACHE_EXISTENCE_AT_OID );
342                systemIndices.put( ApacheSchemaConstants.APACHE_EXISTENCE_AT_OID, presenceIdx );
343                presenceIdx.init( schemaManager, schemaManager
344                    .lookupAttributeTypeRegistry( ApacheSchemaConstants.APACHE_EXISTENCE_AT_OID ), workingDirectory );
345            }
346    
347            if ( oneLevelIdx == null )
348            {
349                oneLevelIdx = new JdbmIndex<Long, E>();
350                oneLevelIdx.setAttributeId( ApacheSchemaConstants.APACHE_ONE_LEVEL_AT_OID );
351                systemIndices.put( ApacheSchemaConstants.APACHE_ONE_LEVEL_AT_OID, oneLevelIdx );
352                oneLevelIdx.init( schemaManager, schemaManager
353                    .lookupAttributeTypeRegistry( ApacheSchemaConstants.APACHE_ONE_LEVEL_AT_OID ), workingDirectory );
354            }
355    
356            if ( oneAliasIdx == null )
357            {
358                oneAliasIdx = new JdbmIndex<Long, E>();
359                oneAliasIdx.setAttributeId( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID );
360                systemIndices.put( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID, oneAliasIdx );
361                oneAliasIdx.init( schemaManager, schemaManager
362                    .lookupAttributeTypeRegistry( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID ), workingDirectory );
363            }
364    
365            if ( subAliasIdx == null )
366            {
367                subAliasIdx = new JdbmIndex<Long, E>();
368                subAliasIdx.setAttributeId( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID );
369                systemIndices.put( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID, subAliasIdx );
370                subAliasIdx.init( schemaManager, schemaManager
371                    .lookupAttributeTypeRegistry( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID ), workingDirectory );
372            }
373    
374            if ( aliasIdx == null )
375            {
376                aliasIdx = new JdbmIndex<String, E>();
377                aliasIdx.setAttributeId( ApacheSchemaConstants.APACHE_ALIAS_AT_OID );
378                systemIndices.put( ApacheSchemaConstants.APACHE_ALIAS_AT_OID, aliasIdx );
379                aliasIdx.init( schemaManager, schemaManager
380                    .lookupAttributeTypeRegistry( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ), workingDirectory );
381            }
382    
383            if ( subLevelIdx == null )
384            {
385                subLevelIdx = new JdbmIndex<Long, E>();
386                subLevelIdx.setAttributeId( ApacheSchemaConstants.APACHE_SUB_LEVEL_AT_OID );
387                systemIndices.put( ApacheSchemaConstants.APACHE_SUB_LEVEL_AT_OID, subLevelIdx );
388                subLevelIdx.init( schemaManager, schemaManager
389                    .lookupAttributeTypeRegistry( ApacheSchemaConstants.APACHE_SUB_LEVEL_AT_OID ), workingDirectory );
390            }
391    
392            if ( entryCsnIdx == null )
393            {
394                entryCsnIdx = new JdbmIndex<String, E>();
395                entryCsnIdx.setAttributeId( SchemaConstants.ENTRY_CSN_AT_OID );
396                systemIndices.put( SchemaConstants.ENTRY_CSN_AT_OID, entryCsnIdx );
397                entryCsnIdx.init( schemaManager, schemaManager
398                    .lookupAttributeTypeRegistry( SchemaConstants.ENTRY_CSN_AT_OID ), workingDirectory );
399            }
400    
401            if ( entryUuidIdx == null )
402            {
403                entryUuidIdx = new JdbmIndex<String, E>();
404                entryUuidIdx.setAttributeId( SchemaConstants.ENTRY_UUID_AT_OID );
405                systemIndices.put( SchemaConstants.ENTRY_UUID_AT_OID, entryUuidIdx );
406                entryUuidIdx.init( schemaManager, schemaManager
407                    .lookupAttributeTypeRegistry( SchemaConstants.ENTRY_UUID_AT_OID ), workingDirectory );
408            }
409    
410            if ( objectClassIdx == null )
411            {
412                objectClassIdx = new JdbmIndex<String, E>();
413                objectClassIdx.setAttributeId( SchemaConstants.OBJECT_CLASS_AT_OID );
414                systemIndices.put( SchemaConstants.OBJECT_CLASS_AT_OID, objectClassIdx );
415                objectClassIdx.init( schemaManager, schemaManager
416                    .lookupAttributeTypeRegistry( SchemaConstants.OBJECT_CLASS_AT_OID ), workingDirectory );
417            }
418        }
419    
420    
421        @SuppressWarnings("unchecked")
422        private void setupUserIndices() throws Exception
423        {
424            if ( ( userIndices != null ) && ( userIndices.size() > 0 ) )
425            {
426                Map<String, Index<?, E, Long>> tmp = new HashMap<String, Index<?, E, Long>>();
427    
428                for ( Index<?, E, Long> index : userIndices.values() )
429                {
430                    String oid = schemaManager.getAttributeTypeRegistry().getOidByName( index.getAttributeId() );
431    
432                    if ( systemIndices.containsKey( oid ) )
433                    {
434                        // Bypass some specific index for AttributeTypes like ObjectClass hich are already
435                        // present in the SystemIndices
436                        continue;
437                    }
438                    AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid );
439    
440                    // Check that the attributeType has an EQUALITY matchingRule
441                    MatchingRule mr = attributeType.getEquality();
442    
443                    if ( mr != null )
444                    {
445                        ( ( JdbmIndex ) index ).init( schemaManager, schemaManager.lookupAttributeTypeRegistry( oid ),
446                            workingDirectory );
447                        tmp.put( oid, index );
448                    }
449                    else
450                    {
451                        LOG.error( I18n.err( I18n.ERR_4, attributeType.getName() ) );
452                    }
453                }
454    
455                userIndices = tmp;
456            }
457            else
458            {
459                userIndices = new HashMap<String, Index<?, E, Long>>();
460            }
461        }
462    
463    
464        /**
465         * Close the partition : we have to close all the userIndices and the master table.
466         * 
467         * @throws Exception lazily thrown on any closer failures to avoid leaving
468         * open files
469         */
470        public synchronized void destroy() throws Exception
471        {
472            LOG.debug( "destroy() called on store for {}", this.suffixDn );
473    
474            if ( !initialized )
475            {
476                return;
477            }
478    
479            List<Index<?, E, Long>> array = new ArrayList<Index<?, E, Long>>();
480            array.addAll( userIndices.values() );
481            array.addAll( systemIndices.values() );
482            MultiException errors = new MultiException( I18n.err( I18n.ERR_577 ) );
483    
484            for ( Index<?, E, Long> index : array )
485            {
486                try
487                {
488                    index.close();
489                    LOG.debug( "Closed {} index for {} partition.", index.getAttributeId(), suffixDn );
490                }
491                catch ( Throwable t )
492                {
493                    LOG.error( I18n.err( I18n.ERR_124 ), t );
494                    errors.addThrowable( t );
495                }
496            }
497    
498            try
499            {
500                master.close();
501                LOG.debug( I18n.err( I18n.ERR_125, suffixDn ) );
502            }
503            catch ( Throwable t )
504            {
505                LOG.error( I18n.err( I18n.ERR_126 ), t );
506                errors.addThrowable( t );
507            }
508    
509            try
510            {
511                recMan.close();
512                LOG.debug( "Closed record manager for {} partition.", suffixDn );
513            }
514            catch ( Throwable t )
515            {
516                LOG.error( I18n.err( I18n.ERR_127 ), t );
517                errors.addThrowable( t );
518            }
519    
520            if ( errors.size() > 0 )
521            {
522                throw errors;
523            }
524    
525            initialized = false;
526        }
527    
528    
529        /**
530         * Gets whether the store is initialized.
531         *
532         * @return true if the partition store is initialized
533         */
534        public boolean isInitialized()
535        {
536            return initialized;
537        }
538    
539    
540        /**
541         * This method is called when the synch thread is waking up, to write
542         * the modified data.
543         * 
544         * @throws Exception on failures to sync database files to disk
545         */
546        public synchronized void sync() throws Exception
547        {
548            if ( !initialized )
549            {
550                return;
551            }
552    
553            List<Index<?, E, Long>> array = new ArrayList<Index<?, E, Long>>();
554            array.addAll( userIndices.values() );
555            array.add( ndnIdx );
556            array.add( updnIdx );
557            array.add( aliasIdx );
558            array.add( oneAliasIdx );
559            array.add( subAliasIdx );
560            array.add( oneLevelIdx );
561            array.add( presenceIdx );
562            array.add( subLevelIdx );
563            array.add( entryCsnIdx );
564            array.add( entryUuidIdx );
565            array.add( objectClassIdx );
566    
567            // Sync all user defined userIndices
568            for ( Index<?, E, Long> idx : array )
569            {
570                idx.sync();
571            }
572    
573            master.sync();
574            recMan.commit();
575        }
576    
577    
578        // ------------------------------------------------------------------------
579        // I N D E X   M E T H O D S
580        // ------------------------------------------------------------------------
581    
582        private <K> JdbmIndex<K, E> convertIndex( Index<K, E, Long> index )
583        {
584            if ( index instanceof JdbmIndex<?, ?> )
585            {
586                return ( JdbmIndex<K, E> ) index;
587            }
588    
589            LOG.warn( "Supplied index {} is not a JdbmIndex.  "
590                + "Will create new JdbmIndex using copied configuration parameters.", index );
591            JdbmIndex<K, E> jdbmIndex = new JdbmIndex<K, E>( index.getAttributeId() );
592            jdbmIndex.setCacheSize( index.getCacheSize() );
593            jdbmIndex.setNumDupLimit( JdbmIndex.DEFAULT_DUPLICATE_LIMIT );
594            jdbmIndex.setWkDirPath( index.getWkDirPath() );
595            return jdbmIndex;
596        }
597    
598    
599        public void setUserIndices( Set<Index<?, E, Long>> userIndices )
600        {
601            protect( "userIndices" );
602            for ( Index<?, E, Long> index : userIndices )
603            {
604                this.userIndices.put( index.getAttributeId(), convertIndex( index ) );
605            }
606        }
607    
608    
609        public Set<Index<?, E, Long>> getUserIndices()
610        {
611            return new HashSet<Index<?, E, Long>>( userIndices.values() );
612        }
613    
614    
615        public void addIndex( Index<?, E, Long> index ) throws Exception
616        {
617            userIndices.put( index.getAttributeId(), convertIndex( index ) );
618        }
619    
620    
621        //------------------------------------------------------------------------
622        // System index
623        //------------------------------------------------------------------------
624        /**
625         * {@inheritDoc}
626         */
627        public Index<String, E, Long> getPresenceIndex()
628        {
629            return presenceIdx;
630        }
631    
632    
633        /**
634         * {@inheritDoc}
635         */
636        public void setPresenceIndex( Index<String, E, Long> index ) throws Exception
637        {
638            protect( "presenceIndex" );
639            presenceIdx = convertIndex( index );
640            systemIndices.put( index.getAttributeId(), presenceIdx );
641        }
642    
643    
644        /**
645         * {@inheritDoc}
646         */
647        public Index<Long, E, Long> getOneLevelIndex()
648        {
649            return oneLevelIdx;
650        }
651    
652    
653        /**
654         * {@inheritDoc}
655         */
656        public void setOneLevelIndex( Index<Long, E, Long> index ) throws Exception
657        {
658            protect( "hierarchyIndex" );
659            oneLevelIdx = convertIndex( index );
660            systemIndices.put( index.getAttributeId(), oneLevelIdx );
661        }
662    
663    
664        /**
665         * {@inheritDoc}
666         */
667        public Index<String, E, Long> getAliasIndex()
668        {
669            return aliasIdx;
670        }
671    
672    
673        /**
674         * {@inheritDoc}
675         */
676        public void setAliasIndex( Index<String, E, Long> index ) throws LdapException
677        {
678            protect( "aliasIndex" );
679            aliasIdx = convertIndex( index );
680            systemIndices.put( index.getAttributeId(), aliasIdx );
681        }
682    
683    
684        /**
685         * {@inheritDoc}
686         */
687        public Index<Long, E, Long> getOneAliasIndex()
688        {
689            return oneAliasIdx;
690        }
691    
692    
693        /**
694         * {@inheritDoc}
695         */
696        public void setOneAliasIndex( Index<Long, E, Long> index ) throws LdapException
697        {
698            protect( "oneAliasIndex" );
699            oneAliasIdx = convertIndex( index );
700            systemIndices.put( index.getAttributeId(), oneAliasIdx );
701        }
702    
703    
704        /**
705         * {@inheritDoc}
706         */
707        public Index<Long, E, Long> getSubAliasIndex()
708        {
709            return subAliasIdx;
710        }
711    
712    
713        /**
714         * {@inheritDoc}
715         */
716        public void setSubAliasIndex( Index<Long, E, Long> index ) throws LdapException
717        {
718            protect( "subAliasIndex" );
719            subAliasIdx = convertIndex( index );
720            systemIndices.put( index.getAttributeId(), subAliasIdx );
721        }
722    
723    
724        /**
725         * {@inheritDoc}
726         */
727        public Index<String, E, Long> getUpdnIndex()
728        {
729            return updnIdx;
730        }
731    
732    
733        /**
734         * {@inheritDoc}
735         */
736        public void setUpdnIndex( Index<String, E, Long> index ) throws LdapException
737        {
738            protect( "updnIndex" );
739            updnIdx = convertIndex( index );
740            systemIndices.put( index.getAttributeId(), updnIdx );
741        }
742    
743    
744        /**
745         * {@inheritDoc}
746         */
747        public Index<String, E, Long> getNdnIndex()
748        {
749            return ndnIdx;
750        }
751    
752    
753        /**
754         * {@inheritDoc}
755         */
756        public void setNdnIndex( Index<String, E, Long> index ) throws LdapException
757        {
758            protect( "ndnIndex" );
759            ndnIdx = convertIndex( index );
760            systemIndices.put( index.getAttributeId(), ndnIdx );
761        }
762    
763    
764        /**
765         * {@inheritDoc}
766         */
767        public Index<Long, E, Long> getSubLevelIndex()
768        {
769            return subLevelIdx;
770        }
771    
772    
773        /**
774         * {@inheritDoc}
775         */
776        public void setSubLevelIndex( Index<Long, E, Long> index ) throws LdapException
777        {
778            protect( "subLevelIndex" );
779            subLevelIdx = convertIndex( index );
780            systemIndices.put( index.getAttributeId(), subLevelIdx );
781        }
782    
783    
784        /**
785         * {@inheritDoc}
786         */
787        public Index<String, E, Long> getObjectClassIndex()
788        {
789            return objectClassIdx;
790        }
791    
792    
793        /**
794         * {@inheritDoc}
795         */
796        public void setObjectClassIndex( Index<String, E, Long> index ) throws LdapException
797        {
798            protect( "objectClassIndex" );
799            objectClassIdx = convertIndex( index );
800            systemIndices.put( index.getAttributeId(), objectClassIdx );
801        }
802    
803    
804        /**
805         * {@inheritDoc}
806         */
807        public Index<String, E, Long> getEntryUuidIndex()
808        {
809            return entryUuidIdx;
810        }
811    
812    
813        /**
814         * {@inheritDoc}
815         */
816        public void setEntryUuidIndex( Index<String, E, Long> index ) throws LdapException
817        {
818            protect( "entryUuidIndex" );
819            entryUuidIdx = convertIndex( index );
820            systemIndices.put( index.getAttributeId(), entryUuidIdx );
821        }
822    
823    
824        /**
825         * {@inheritDoc}
826         */
827        public Index<String, E, Long> getEntryCsnIndex()
828        {
829            return entryCsnIdx;
830        }
831    
832    
833        /**
834         * {@inheritDoc}
835         */
836        public void setEntryCsnIndex( Index<String, E, Long> index ) throws LdapException
837        {
838            protect( "entryCsnIndex" );
839            entryCsnIdx = convertIndex( index );
840            systemIndices.put( index.getAttributeId(), entryCsnIdx );
841        }
842    
843    
844        public Iterator<String> userIndices()
845        {
846            return userIndices.keySet().iterator();
847        }
848    
849    
850        public Iterator<String> systemIndices()
851        {
852            return systemIndices.keySet().iterator();
853        }
854    
855    
856        public boolean hasIndexOn( String id ) throws LdapException
857        {
858            return hasUserIndexOn( id ) || hasSystemIndexOn( id );
859        }
860    
861    
862        public boolean hasUserIndexOn( String id ) throws LdapException
863        {
864            return userIndices.containsKey( schemaManager.getAttributeTypeRegistry().getOidByName( id ) );
865        }
866    
867    
868        public boolean hasSystemIndexOn( String id ) throws LdapException
869        {
870            return systemIndices.containsKey( schemaManager.getAttributeTypeRegistry().getOidByName( id ) );
871        }
872    
873    
874        public Index<?, E, Long> getIndex( String id ) throws IndexNotFoundException
875        {
876            try
877            {
878                id = schemaManager.getAttributeTypeRegistry().getOidByName( id );
879            }
880            catch ( LdapException e )
881            {
882                String msg = I18n.err( I18n.ERR_128, id );
883                LOG.error( msg, e );
884                throw new IndexNotFoundException( msg, id, e );
885            }
886    
887            if ( userIndices.containsKey( id ) )
888            {
889                return userIndices.get( id );
890            }
891            if ( systemIndices.containsKey( id ) )
892            {
893                return systemIndices.get( id );
894            }
895    
896            throw new IndexNotFoundException( I18n.err( I18n.ERR_3, id, name ) );
897        }
898    
899    
900        public Index<?, E, Long> getUserIndex( String id ) throws IndexNotFoundException
901        {
902            try
903            {
904                id = schemaManager.getAttributeTypeRegistry().getOidByName( id );
905            }
906            catch ( LdapException e )
907            {
908                String msg = I18n.err( I18n.ERR_128, id );
909                LOG.error( msg, e );
910                throw new IndexNotFoundException( msg, id, e );
911            }
912    
913            if ( userIndices.containsKey( id ) )
914            {
915                return userIndices.get( id );
916            }
917    
918            throw new IndexNotFoundException( I18n.err( I18n.ERR_3, id, name ) );
919        }
920    
921    
922        public Index<?, E, Long> getSystemIndex( String id ) throws IndexNotFoundException
923        {
924            try
925            {
926                id = schemaManager.getAttributeTypeRegistry().getOidByName( id );
927            }
928            catch ( LdapException e )
929            {
930                String msg = I18n.err( I18n.ERR_128, id );
931                LOG.error( msg, e );
932                throw new IndexNotFoundException( msg, id, e );
933            }
934    
935            if ( systemIndices.containsKey( id ) )
936            {
937                return systemIndices.get( id );
938            }
939    
940            throw new IndexNotFoundException( I18n.err( I18n.ERR_2, id, name ) );
941        }
942    
943    
944        public Long getEntryId( String dn ) throws Exception
945        {
946            return ndnIdx.forwardLookup( dn );
947        }
948    
949    
950        public String getEntryDn( Long id ) throws Exception
951        {
952            return ndnIdx.reverseLookup( id );
953        }
954    
955    
956        /**
957         * Gets the Long id of an entry's parent using the child entry's
958         * normalized DN. Note that the suffix entry returns 0, which does not
959         * map to any entry.
960         *
961         * @param dn the normalized distinguished name of the child
962         * @return the id of the parent entry or zero if the suffix entry the
963         * normalized suffix DN string is used
964         * @throws Exception on failures to access the underlying store
965         */
966        public Long getParentId( String dn ) throws Exception
967        {
968            Long childId = ndnIdx.forwardLookup( dn );
969            return oneLevelIdx.reverseLookup( childId );
970        }
971    
972    
973        public Long getParentId( Long childId ) throws Exception
974        {
975            return oneLevelIdx.reverseLookup( childId );
976        }
977    
978    
979        public String getEntryUpdn( Long id ) throws Exception
980        {
981            return updnIdx.reverseLookup( id );
982        }
983    
984    
985        public String getEntryUpdn( String dn ) throws Exception
986        {
987            Long id = ndnIdx.forwardLookup( dn );
988            return updnIdx.reverseLookup( id );
989        }
990    
991    
992        public int count() throws Exception
993        {
994            return master.count();
995        }
996    
997    
998        /**
999         * Removes the index entries for an alias before the entry is deleted from
1000         * the master table.
1001         * 
1002         * @todo Optimize this by walking the hierarchy index instead of the name 
1003         * @param aliasId the id of the alias entry in the master table
1004         * @throws LdapException if we cannot parse ldap names
1005         * @throws Exception if we cannot delete index values in the database
1006         */
1007        private void dropAliasIndices( Long aliasId ) throws Exception
1008        {
1009            String targetDn = aliasIdx.reverseLookup( aliasId );
1010            Long targetId = getEntryId( targetDn );
1011            String aliasDn = getEntryDn( aliasId );
1012            DN aliasDN = ( DN ) new DN( aliasDn );
1013    
1014            DN ancestorDn = ( DN ) aliasDN.clone();
1015            ancestorDn.remove( aliasDN.size() - 1 );
1016            Long ancestorId = getEntryId( ancestorDn.getNormName() );
1017    
1018            /*
1019             * We cannot just drop all tuples in the one level and subtree userIndices
1020             * linking baseIds to the targetId.  If more than one alias refers to
1021             * the target then droping all tuples with a value of targetId would
1022             * make all other aliases to the target inconsistent.
1023             * 
1024             * We need to walk up the path of alias ancestors until we reach the 
1025             * upSuffix, deleting each ( ancestorId, targetId ) tuple in the
1026             * subtree scope alias.  We only need to do this for the direct parent
1027             * of the alias on the one level subtree.
1028             */
1029            oneAliasIdx.drop( ancestorId, targetId );
1030            subAliasIdx.drop( ancestorId, targetId );
1031    
1032            while ( !ancestorDn.equals( normSuffix ) && ancestorDn.size() > normSuffix.size() )
1033            {
1034                ancestorDn = ( DN ) ancestorDn.getPrefix( ancestorDn.size() - 1 );
1035                ancestorId = getEntryId( ancestorDn.getNormName() );
1036    
1037                subAliasIdx.drop( ancestorId, targetId );
1038            }
1039    
1040            // Drops all alias tuples pointing to the id of the alias to be deleted
1041            aliasIdx.drop( aliasId );
1042        }
1043    
1044    
1045        /**
1046         * Adds userIndices for an aliasEntry to be added to the database while checking
1047         * for constrained alias constructs like alias cycles and chaining.
1048         * 
1049         * @param aliasDn normalized distinguished name for the alias entry
1050         * @param aliasTarget the user provided aliased entry dn as a string
1051         * @param aliasId the id of alias entry to add
1052         * @throws LdapException if index addition fails, and if the alias is
1053         * not allowed due to chaining or cycle formation.
1054         * @throws Exception if the wrappedCursor btrees cannot be altered
1055         */
1056        private void addAliasIndices( Long aliasId, DN aliasDn, String aliasTarget ) throws Exception
1057        {
1058            DN normalizedAliasTargetDn; // Name value of aliasedObjectName
1059            Long targetId; // Id of the aliasedObjectName
1060            DN ancestorDn; // Name of an alias entry relative
1061            Long ancestorId; // Id of an alias entry relative
1062    
1063            // Access aliasedObjectName, normalize it and generate the Name 
1064            normalizedAliasTargetDn = new DN( aliasTarget );
1065            normalizedAliasTargetDn.normalize( schemaManager.getNormalizerMapping() );
1066    
1067            /*
1068             * Check For Cycles
1069             * 
1070             * Before wasting time to lookup more values we check using the target
1071             * dn to see if we have the possible formation of an alias cycle.  This
1072             * happens when the alias refers back to a target that is also a 
1073             * relative of the alias entry.  For detection we test if the aliased
1074             * entry Dn starts with the target Dn.  If it does then we know the 
1075             * aliased target is a relative and we have a perspecitive cycle.
1076             */
1077            if ( aliasDn.isChildOf( normalizedAliasTargetDn ) )
1078            {
1079                if ( aliasDn.equals( normalizedAliasTargetDn ) )
1080                {
1081                    String msg = I18n.err( I18n.ERR_223 );
1082                    LdapAliasDereferencingException e = new LdapAliasDereferencingException( msg );
1083                    //e.setResolvedName( aliasDn );
1084                    throw e;
1085                }
1086    
1087                String msg = I18n.err( I18n.ERR_224, aliasTarget, aliasDn );
1088                LdapAliasDereferencingException e = new LdapAliasDereferencingException( msg );
1089                //e.setResolvedName( aliasDn );
1090                throw e;
1091            }
1092    
1093            /*
1094             * Check For Aliases External To Naming Context
1095             * 
1096             * id may be null but the alias may be to a valid entry in 
1097             * another namingContext.  Such aliases are not allowed and we
1098             * need to point it out to the user instead of saying the target
1099             * does not exist when it potentially could outside of this upSuffix.
1100             */
1101            if ( !normalizedAliasTargetDn.isChildOf( normSuffix ) )
1102            {
1103                String msg = I18n.err( I18n.ERR_225, upSuffix.getName() );
1104                LdapAliasDereferencingException e = new LdapAliasDereferencingException( msg );
1105                //e.setResolvedName( aliasDn );
1106                throw e;
1107            }
1108    
1109            // L O O K U P   T A R G E T   I D
1110            targetId = ndnIdx.forwardLookup( normalizedAliasTargetDn.getNormName() );
1111    
1112            /*
1113             * Check For Target Existence
1114             * 
1115             * We do not allow the creation of inconsistent aliases.  Aliases should
1116             * not be broken links.  If the target does not exist we start screaming
1117             */
1118            if ( null == targetId )
1119            {
1120                // Complain about target not existing
1121                String msg = I18n.err( I18n.ERR_581, aliasDn.getName(), aliasTarget );
1122                LdapAliasException e = new LdapAliasException( msg );
1123                //e.setResolvedName( aliasDn );
1124                throw e;
1125            }
1126    
1127            /*
1128             * Detect Direct Alias Chain Creation
1129             * 
1130             * Rather than resusitate the target to test if it is an alias and fail
1131             * due to chaing creation we use the alias index to determine if the
1132             * target is an alias.  Hence if the alias we are about to create points
1133             * to another alias as its target in the aliasedObjectName attribute, 
1134             * then we have a situation where an alias chain is being created.  
1135             * Alias chaining is not allowed so we throw and exception. 
1136             */
1137            if ( null != aliasIdx.reverseLookup( targetId ) )
1138            {
1139                String msg = I18n.err( I18n.ERR_227 );
1140                LdapAliasDereferencingException e = new LdapAliasDereferencingException( msg );
1141                //e.setResolvedName( aliasDn );
1142                throw e;
1143            }
1144    
1145            // Add the alias to the simple alias index
1146            aliasIdx.add( normalizedAliasTargetDn.getNormName(), aliasId );
1147    
1148            /*
1149             * Handle One Level Scope Alias Index
1150             * 
1151             * The first relative is special with respect to the one level alias
1152             * index.  If the target is not a sibling of the alias then we add the
1153             * index entry maping the parent's id to the aliased target id.
1154             */
1155            ancestorDn = ( DN ) aliasDn.clone();
1156            ancestorDn.remove( aliasDn.size() - 1 );
1157            ancestorId = getEntryId( ancestorDn.getNormName() );
1158    
1159            // check if alias parent and aliased entry are the same
1160            DN normalizedAliasTargetParentDn = ( DN ) normalizedAliasTargetDn.clone();
1161            normalizedAliasTargetParentDn.remove( normalizedAliasTargetDn.size() - 1 );
1162            if ( !aliasDn.isChildOf( normalizedAliasTargetParentDn ) )
1163            {
1164                oneAliasIdx.add( ancestorId, targetId );
1165            }
1166    
1167            /*
1168             * Handle Sub Level Scope Alias Index
1169             * 
1170             * Walk the list of relatives from the parents up to the upSuffix, testing
1171             * to see if the alias' target is a descendant of the relative.  If the
1172             * alias target is not a descentant of the relative it extends the scope
1173             * and is added to the sub tree scope alias index.  The upSuffix node is
1174             * ignored since everything is under its scope.  The first loop 
1175             * iteration shall handle the parents.
1176             */
1177            while ( !ancestorDn.equals( normSuffix ) && null != ancestorId )
1178            {
1179                if ( !NamespaceTools.isDescendant( ancestorDn, normalizedAliasTargetDn ) )
1180                {
1181                    subAliasIdx.add( ancestorId, targetId );
1182                }
1183    
1184                ancestorDn.remove( ancestorDn.size() - 1 );
1185                ancestorId = getEntryId( ancestorDn.getNormName() );
1186            }
1187        }
1188    
1189    
1190        /**
1191         * {@inheritDoc}
1192         * TODO : We should be able to revert all the changes made to index 
1193         * if something went wrong. Also the index should auto-repair : if
1194         * an entry does not exist in the Master table, then the index must be updated to reflect this.
1195         */
1196        @SuppressWarnings("unchecked")
1197        public synchronized void add( ServerEntry entry ) throws Exception
1198        {
1199            if ( entry instanceof ClonedServerEntry )
1200            {
1201                throw new Exception( I18n.err( I18n.ERR_215 ) );
1202            }
1203    
1204            Long parentId;
1205            Long id = master.getNextId();
1206    
1207            //
1208            // Suffix entry cannot have a parent since it is the root so it is 
1209            // capped off using the zero value which no entry can have since 
1210            // entry sequences start at 1.
1211            //
1212            DN entryDn = entry.getDn();
1213            DN parentDn = null;
1214    
1215            if ( entryDn.getNormName().equals( normSuffix.getNormName() ) )
1216            {
1217                parentId = 0L;
1218            }
1219            else
1220            {
1221                parentDn = ( DN ) entryDn.clone();
1222                parentDn.remove( parentDn.size() - 1 );
1223                parentId = getEntryId( parentDn.getNormName() );
1224            }
1225    
1226            // don't keep going if we cannot find the parent Id
1227            if ( parentId == null )
1228            {
1229                throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_216, parentDn ) );
1230            }
1231    
1232            EntryAttribute objectClass = entry.get( OBJECT_CLASS_AT );
1233    
1234            if ( objectClass == null )
1235            {
1236                String msg = I18n.err( I18n.ERR_217, entryDn.getName(), entry );
1237                ResultCodeEnum rc = ResultCodeEnum.OBJECT_CLASS_VIOLATION;
1238                LdapSchemaViolationException e = new LdapSchemaViolationException( rc, msg );
1239                //e.setResolvedName( entryDn );
1240                throw e;
1241            }
1242    
1243            // Start adding the system userIndices
1244            // Why bother doing a lookup if this is not an alias.
1245            // First, the ObjectClass index
1246            for ( Value<?> value : objectClass )
1247            {
1248                objectClassIdx.add( value.getString(), id );
1249            }
1250    
1251            if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
1252            {
1253                EntryAttribute aliasAttr = entry.get( ALIASED_OBJECT_NAME_AT );
1254                addAliasIndices( id, entryDn, aliasAttr.getString() );
1255            }
1256    
1257            if ( !Character.isDigit( entryDn.getNormName().charAt( 0 ) ) )
1258            {
1259                throw new IllegalStateException( I18n.err( I18n.ERR_218, entryDn.getNormName() ) );
1260            }
1261    
1262            ndnIdx.add( entryDn.getNormName(), id );
1263            updnIdx.add( entryDn.getName(), id );
1264            oneLevelIdx.add( parentId, id );
1265    
1266            // Update the EntryCsn index
1267            EntryAttribute entryCsn = entry.get( ENTRY_CSN_AT );
1268    
1269            if ( entryCsn == null )
1270            {
1271                String msg = I18n.err( I18n.ERR_219, entryDn.getName(), entry );
1272                throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg );
1273            }
1274    
1275            entryCsnIdx.add( entryCsn.getString(), id );
1276    
1277            // Update the EntryUuid index
1278            EntryAttribute entryUuid = entry.get( ENTRY_UUID_AT );
1279    
1280            if ( entryUuid == null )
1281            {
1282                String msg = I18n.err( I18n.ERR_220, entryDn.getName(), entry );
1283                throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg );
1284            }
1285    
1286            entryUuidIdx.add( entryUuid.getString(), id );
1287    
1288            Long tempId = parentId;
1289    
1290            while ( ( tempId != null ) && ( tempId != 0 ) && ( tempId != 1 ) )
1291            {
1292                subLevelIdx.add( tempId, id );
1293                tempId = getParentId( tempId );
1294            }
1295    
1296            // making entry an ancestor/descendent of itself in sublevel index
1297            subLevelIdx.add( id, id );
1298    
1299            // Now work on the user defined userIndices
1300            for ( EntryAttribute attribute : entry )
1301            {
1302                String attributeOid = attribute.getAttributeType().getOid();
1303    
1304                if ( hasUserIndexOn( attributeOid ) )
1305                {
1306                    Index<Object, E, Long> idx = ( Index<Object, E, Long> ) getUserIndex( attributeOid );
1307    
1308                    // here lookup by attributeId is OK since we got attributeId from 
1309                    // the entry via the enumeration - it's in there as is for sure
1310    
1311                    for ( Value<?> value : attribute )
1312                    {
1313                        idx.add( value.get(), id );
1314                    }
1315    
1316                    // Adds only those attributes that are indexed
1317                    presenceIdx.add( attributeOid, id );
1318                }
1319            }
1320    
1321            master.put( id, entry );
1322    
1323            if ( isSyncOnWrite )
1324            {
1325                sync();
1326            }
1327        }
1328    
1329    
1330        public ServerEntry lookup( Long id ) throws Exception
1331        {
1332            return ( ServerEntry ) master.get( id );
1333        }
1334    
1335    
1336        /**
1337         * {@inheritDoc}
1338         */
1339        public synchronized void delete( Long id ) throws Exception
1340        {
1341            ServerEntry entry = lookup( id );
1342            Long parentId = getParentId( id );
1343    
1344            EntryAttribute objectClass = entry.get( OBJECT_CLASS_AT );
1345    
1346            if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
1347            {
1348                dropAliasIndices( id );
1349            }
1350    
1351            for ( Value<?> value : objectClass )
1352            {
1353                objectClassIdx.drop( value.getString(), id );
1354            }
1355    
1356            ndnIdx.drop( id );
1357            updnIdx.drop( id );
1358            oneLevelIdx.drop( id );
1359            entryCsnIdx.drop( id );
1360            entryUuidIdx.drop( id );
1361    
1362            if ( id != 1 )
1363            {
1364                subLevelIdx.drop( id );
1365            }
1366    
1367            // Remove parent's reference to entry only if entry is not the upSuffix
1368            if ( !parentId.equals( 0L ) )
1369            {
1370                oneLevelIdx.drop( parentId, id );
1371            }
1372    
1373            for ( EntryAttribute attribute : entry )
1374            {
1375                String attributeOid = attribute.getAttributeType().getOid();
1376    
1377                if ( hasUserIndexOn( attributeOid ) )
1378                {
1379                    Index<?, E, Long> index = getUserIndex( attributeOid );
1380    
1381                    // here lookup by attributeId is ok since we got attributeId from 
1382                    // the entry via the enumeration - it's in there as is for sure
1383                    for ( Value<?> value : attribute )
1384                    {
1385                        ( ( JdbmIndex ) index ).drop( value.get(), id );
1386                    }
1387    
1388                    presenceIdx.drop( attributeOid, id );
1389                }
1390            }
1391    
1392            master.delete( id );
1393    
1394            if ( isSyncOnWrite )
1395            {
1396                sync();
1397            }
1398        }
1399    
1400    
1401        /**
1402         * Gets an IndexEntry Cursor over the child nodes of an entry.
1403         *
1404         * @param id the id of the parent entry
1405         * @return an IndexEntry Cursor over the child entries
1406         * @throws Exception on failures to access the underlying store
1407         */
1408        public IndexCursor<Long, E, Long> list( Long id ) throws Exception
1409        {
1410            IndexCursor<Long, E, Long> cursor = oneLevelIdx.forwardCursor( id );
1411            cursor.beforeValue( id, null );
1412            return cursor;
1413        }
1414    
1415    
1416        public int getChildCount( Long id ) throws Exception
1417        {
1418            return oneLevelIdx.count( id );
1419        }
1420    
1421    
1422        public DN getSuffix()
1423        {
1424            return normSuffix;
1425        }
1426    
1427    
1428        public DN getUpSuffix()
1429        {
1430            return upSuffix;
1431        }
1432    
1433    
1434        public void setProperty( String propertyName, String propertyValue ) throws Exception
1435        {
1436            master.setProperty( propertyName, propertyValue );
1437        }
1438    
1439    
1440        public String getProperty( String propertyName ) throws Exception
1441        {
1442            return master.getProperty( propertyName );
1443        }
1444    
1445    
1446        /**
1447         * Adds a set of attribute values while affecting the appropriate userIndices.
1448         * The entry is not persisted: it is only changed in anticipation for a put 
1449         * into the master table.
1450         *
1451         * @param id the primary key of the entry
1452         * @param entry the entry to alter
1453         * @param mods the attribute and values to add 
1454         * @throws Exception if index alteration or attribute addition fails
1455         */
1456        @SuppressWarnings("unchecked")
1457        private void add( Long id, ServerEntry entry, EntryAttribute mods ) throws Exception
1458        {
1459            if ( entry instanceof ClonedServerEntry )
1460            {
1461                throw new Exception( I18n.err( I18n.ERR_215 ) );
1462            }
1463    
1464            String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
1465    
1466            // Special case for the ObjectClass index
1467            if ( modsOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
1468            {
1469                for ( Value<?> value : mods )
1470                {
1471                    objectClassIdx.drop( value.getString(), id );
1472                }
1473            }
1474            else if ( hasUserIndexOn( modsOid ) )
1475            {
1476                Index<?, E, Long> index = getUserIndex( modsOid );
1477    
1478                for ( Value<?> value : mods )
1479                {
1480                    ( ( JdbmIndex ) index ).add( value.get(), id );
1481                }
1482    
1483                // If the attr didn't exist for this id add it to existence index
1484                if ( !presenceIdx.forward( modsOid, id ) )
1485                {
1486                    presenceIdx.add( modsOid, id );
1487                }
1488            }
1489    
1490            // add all the values in mods to the same attribute in the entry
1491            AttributeType type = schemaManager.lookupAttributeTypeRegistry( modsOid );
1492    
1493            for ( Value<?> value : mods )
1494            {
1495                entry.add( type, value );
1496            }
1497    
1498            if ( modsOid.equals( SchemaConstants.ALIASED_OBJECT_NAME_AT_OID ) )
1499            {
1500                String ndnStr = ndnIdx.reverseLookup( id );
1501                addAliasIndices( id, new DN( ndnStr ), mods.getString() );
1502            }
1503        }
1504    
1505    
1506        /**
1507         * Completely removes the set of values for an attribute having the values 
1508         * supplied while affecting the appropriate userIndices.  The entry is not
1509         * persisted: it is only changed in anticipation for a put into the master 
1510         * table.  Note that an empty attribute w/o values will remove all the 
1511         * values within the entry where as an attribute w/ values will remove those
1512         * attribute values it contains.
1513         *
1514         * @param id the primary key of the entry
1515         * @param entry the entry to alter
1516         * @param mods the attribute and its values to delete
1517         * @throws Exception if index alteration or attribute modification fails.
1518         */
1519        @SuppressWarnings("unchecked")
1520        private void remove( Long id, ServerEntry entry, EntryAttribute mods ) throws Exception
1521        {
1522            if ( entry instanceof ClonedServerEntry )
1523            {
1524                throw new Exception( I18n.err( I18n.ERR_215 ) );
1525            }
1526    
1527            String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
1528    
1529            // Special case for the ObjectClass index
1530            if ( modsOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
1531            {
1532                for ( Value<?> value : mods )
1533                {
1534                    objectClassIdx.drop( value.getString(), id );
1535                }
1536            }
1537            else if ( hasUserIndexOn( modsOid ) )
1538            {
1539                Index<?, E, Long> index = getUserIndex( modsOid );
1540    
1541                for ( Value<?> value : mods )
1542                {
1543                    ( ( JdbmIndex ) index ).drop( value.get(), id );
1544                }
1545    
1546                /* 
1547                 * If no attribute values exist for this entryId in the index then
1548                 * we remove the presence index entry for the removed attribute.
1549                 */
1550                if ( null == index.reverseLookup( id ) )
1551                {
1552                    presenceIdx.drop( modsOid, id );
1553                }
1554            }
1555    
1556            AttributeType attrType = schemaManager.lookupAttributeTypeRegistry( modsOid );
1557            /*
1558             * If there are no attribute values in the modifications then this 
1559             * implies the compelete removal of the attribute from the entry. Else
1560             * we remove individual attribute values from the entry in mods one 
1561             * at a time.
1562             */
1563            if ( mods.size() == 0 )
1564            {
1565                entry.removeAttributes( attrType );
1566            }
1567            else
1568            {
1569                EntryAttribute entryAttr = entry.get( attrType );
1570    
1571                for ( Value<?> value : mods )
1572                {
1573                    entryAttr.remove( value );
1574                }
1575    
1576                // if nothing is left just remove empty attribute
1577                if ( entryAttr.size() == 0 )
1578                {
1579                    entry.removeAttributes( entryAttr.getId() );
1580                }
1581            }
1582    
1583            // Aliases->single valued comp/partial attr removal is not relevant here
1584            if ( modsOid.equals( SchemaConstants.ALIASED_OBJECT_NAME_AT_OID ) )
1585            {
1586                dropAliasIndices( id );
1587            }
1588        }
1589    
1590    
1591        /**
1592         * Completely replaces the existing set of values for an attribute with the
1593         * modified values supplied affecting the appropriate userIndices.  The entry
1594         * is not persisted: it is only changed in anticipation for a put into the
1595         * master table.
1596         *
1597         * @param id the primary key of the entry
1598         * @param entry the entry to alter
1599         * @param mods the replacement attribute and values
1600         * @throws Exception if index alteration or attribute modification 
1601         * fails.
1602         */
1603        @SuppressWarnings("unchecked")
1604        private void replace( Long id, ServerEntry entry, EntryAttribute mods ) throws Exception
1605        {
1606            if ( entry instanceof ClonedServerEntry )
1607            {
1608                throw new Exception( I18n.err( I18n.ERR_215 ) );
1609            }
1610    
1611            String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
1612    
1613            // Special case for the ObjectClass index
1614            if ( modsOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
1615            {
1616                // if the id exists in the index drop all existing attribute 
1617                // value index entries and add new ones
1618                if ( objectClassIdx.reverse( id ) )
1619                {
1620                    objectClassIdx.drop( id );
1621                }
1622    
1623                for ( Value<?> value : mods )
1624                {
1625                    objectClassIdx.add( value.getString(), id );
1626                }
1627            }
1628            else if ( hasUserIndexOn( modsOid ) )
1629            {
1630                Index<?, E, Long> index = getUserIndex( modsOid );
1631    
1632                // if the id exists in the index drop all existing attribute 
1633                // value index entries and add new ones
1634                if ( index.reverse( id ) )
1635                {
1636                    ( ( JdbmIndex<?, E> ) index ).drop( id );
1637                }
1638    
1639                for ( Value<?> value : mods )
1640                {
1641                    ( ( JdbmIndex<Object, E> ) index ).add( value.get(), id );
1642                }
1643    
1644                /* 
1645                 * If no attribute values exist for this entryId in the index then
1646                 * we remove the presence index entry for the removed attribute.
1647                 */
1648                if ( null == index.reverseLookup( id ) )
1649                {
1650                    presenceIdx.drop( modsOid, id );
1651                }
1652            }
1653    
1654            String aliasAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName(
1655                SchemaConstants.ALIASED_OBJECT_NAME_AT );
1656    
1657            if ( modsOid.equals( aliasAttributeOid ) )
1658            {
1659                dropAliasIndices( id );
1660            }
1661    
1662            // replaces old attributes with new modified ones if they exist
1663            if ( mods.size() > 0 )
1664            {
1665                entry.put( mods );
1666            }
1667            else
1668            // removes old attributes if new replacements do not exist
1669            {
1670                entry.remove( mods );
1671            }
1672    
1673            if ( modsOid.equals( aliasAttributeOid ) && mods.size() > 0 )
1674            {
1675                String ndnStr = ndnIdx.reverseLookup( id );
1676                addAliasIndices( id, new DN( ndnStr ), mods.getString() );
1677            }
1678        }
1679    
1680    
1681        public void modify( DN dn, ModificationOperation modOp, ServerEntry mods ) throws Exception
1682        {
1683            if ( mods instanceof ClonedServerEntry )
1684            {
1685                throw new Exception( I18n.err( I18n.ERR_215 ) );
1686            }
1687    
1688            Long id = getEntryId( dn.getNormName() );
1689            ServerEntry entry = ( ServerEntry ) master.get( id );
1690    
1691            for ( AttributeType attributeType : mods.getAttributeTypes() )
1692            {
1693                EntryAttribute attr = mods.get( attributeType );
1694    
1695                switch ( modOp )
1696                {
1697                    case ADD_ATTRIBUTE:
1698                        add( id, entry, attr );
1699                        break;
1700    
1701                    case REMOVE_ATTRIBUTE:
1702                        remove( id, entry, attr );
1703                        break;
1704    
1705                    case REPLACE_ATTRIBUTE:
1706                        replace( id, entry, attr );
1707    
1708                        break;
1709    
1710                    default:
1711                        throw new LdapException( I18n.err( I18n.ERR_221 ) );
1712                }
1713            }
1714    
1715            master.put( id, entry );
1716    
1717            if ( isSyncOnWrite )
1718            {
1719                sync();
1720            }
1721        }
1722    
1723    
1724        public void modify( DN dn, List<Modification> mods ) throws Exception
1725        {
1726            Long id = getEntryId( dn.getNormName() );
1727            ServerEntry entry = ( ServerEntry ) master.get( id );
1728    
1729            for ( Modification mod : mods )
1730            {
1731                EntryAttribute attrMods = mod.getAttribute();
1732    
1733                switch ( mod.getOperation() )
1734                {
1735                    case ADD_ATTRIBUTE:
1736                        add( id, entry, attrMods );
1737                        break;
1738    
1739                    case REMOVE_ATTRIBUTE:
1740                        remove( id, entry, attrMods );
1741                        break;
1742    
1743                    case REPLACE_ATTRIBUTE:
1744                        replace( id, entry, attrMods );
1745                        break;
1746    
1747                    default:
1748                        throw new LdapException( I18n.err( I18n.ERR_221 ) );
1749                }
1750            }
1751    
1752            master.put( id, entry );
1753    
1754            if ( isSyncOnWrite )
1755            {
1756                sync();
1757            }
1758        }
1759    
1760    
1761        /**
1762         * Changes the relative distinguished name of an entry specified by a 
1763         * distinguished name with the optional removal of the old RDN attribute
1764         * value from the entry.  Name changes propagate down as dn changes to the 
1765         * descendants of the entry where the RDN changed. 
1766         * 
1767         * An RDN change operation does not change parent child relationships.  It 
1768         * merely propagates a name change at a point in the DIT where the RDN is 
1769         * changed. The change propagates down the subtree rooted at the 
1770         * distinguished name specified.
1771         *
1772         * @param dn the normalized distinguished name of the entry to alter
1773         * @param newRdn the new RDN to set
1774         * @param deleteOldRdn whether or not to remove the old RDN attr/val
1775         * @throws Exception if there are any errors propagating the name changes
1776         */
1777        @SuppressWarnings("unchecked")
1778        public void rename( DN dn, RDN newRdn, boolean deleteOldRdn ) throws Exception
1779        {
1780            Long id = getEntryId( dn.getNormName() );
1781            ServerEntry entry = lookup( id );
1782            DN updn = entry.getDn();
1783    
1784            /* 
1785             * H A N D L E   N E W   R D N
1786             * ====================================================================
1787             * Add the new RDN attribute to the entry.  If an index exists on the 
1788             * new RDN attribute we add the index for this attribute value pair.
1789             * Also we make sure that the presence index shows the existence of the
1790             * new RDN attribute within this entry.
1791             */
1792    
1793            for ( AVA newAtav : newRdn )
1794            {
1795                String newNormType = newAtav.getNormType();
1796                Object newNormValue = newAtav.getNormValue().get();
1797                AttributeType newRdnAttrType = schemaManager.lookupAttributeTypeRegistry( newNormType );
1798    
1799                entry.add( newRdnAttrType, newAtav.getUpValue() );
1800    
1801                if ( hasUserIndexOn( newNormType ) )
1802                {
1803                    Index<?, E, Long> index = getUserIndex( newNormType );
1804                    ( ( JdbmIndex ) index ).add( newNormValue, id );
1805    
1806                    // Make sure the altered entry shows the existence of the new attrib
1807                    if ( !presenceIdx.forward( newNormType, id ) )
1808                    {
1809                        presenceIdx.add( newNormType, id );
1810                    }
1811                }
1812            }
1813    
1814            /*
1815             * H A N D L E   O L D   R D N
1816             * ====================================================================
1817             * If the old RDN is to be removed we need to get the attribute and 
1818             * value for it.  Keep in mind the old RDN need not be based on the 
1819             * same attr as the new one.  We remove the RDN value from the entry
1820             * and remove the value/id tuple from the index on the old RDN attr
1821             * if any.  We also test if the delete of the old RDN index tuple 
1822             * removed all the attribute values of the old RDN using a reverse
1823             * lookup.  If so that means we blew away the last value of the old 
1824             * RDN attribute.  In this case we need to remove the attrName/id 
1825             * tuple from the presence index.
1826             * 
1827             * We only remove an ATAV of the old RDN if it is not included in the
1828             * new RDN.
1829             */
1830    
1831            if ( deleteOldRdn )
1832            {
1833                RDN oldRdn = updn.getRdn();
1834                for ( AVA oldAtav : oldRdn )
1835                {
1836                    // check if the new ATAV is part of the old RDN
1837                    // if that is the case we do not remove the ATAV
1838                    boolean mustRemove = true;
1839                    for ( AVA newAtav : newRdn )
1840                    {
1841                        if ( oldAtav.equals( newAtav ) )
1842                        {
1843                            mustRemove = false;
1844                            break;
1845                        }
1846                    }
1847    
1848                    if ( mustRemove )
1849                    {
1850                        String oldNormType = oldAtav.getNormType();
1851                        String oldNormValue = oldAtav.getNormValue().getString();
1852                        AttributeType oldRdnAttrType = schemaManager.lookupAttributeTypeRegistry( oldNormType );
1853                        entry.remove( oldRdnAttrType, oldNormValue );
1854    
1855                        if ( hasUserIndexOn( oldNormType ) )
1856                        {
1857                            Index<?, E, Long> index = getUserIndex( oldNormType );
1858                            ( ( JdbmIndex ) index ).drop( oldNormValue, id );
1859    
1860                            /*
1861                             * If there is no value for id in this index due to our
1862                             * drop above we remove the oldRdnAttr from the presence idx
1863                             */
1864                            if ( null == index.reverseLookup( id ) )
1865                            {
1866                                presenceIdx.drop( oldNormType, id );
1867                            }
1868                        }
1869                    }
1870                }
1871            }
1872    
1873            /*
1874             * H A N D L E   D N   C H A N G E
1875             * ====================================================================
1876             * 1) Build the new user defined distinguished name
1877             *      - clone / copy old updn
1878             *      - remove old upRdn from copy
1879             *      - add the new upRdn to the copy
1880             * 2) Make call to recursive modifyDn method to change the names of the
1881             *    entry and its descendants
1882             */
1883    
1884            DN newUpdn = ( DN ) updn.clone(); // copy da old updn
1885            newUpdn.remove( newUpdn.size() - 1 ); // remove old upRdn
1886            newUpdn.add( newRdn.getName() ); // add da new upRdn
1887    
1888            // gotta normalize cuz this thang is cloned and not normalized by default
1889            newUpdn.normalize( schemaManager.getNormalizerMapping() );
1890    
1891            modifyDn( id, newUpdn, false ); // propagate dn changes
1892    
1893            // Update the current entry
1894            entry.setDn( newUpdn );
1895            master.put( id, entry );
1896    
1897            if ( isSyncOnWrite )
1898            {
1899                sync();
1900            }
1901        }
1902    
1903    
1904        /*
1905         * The move operation severs a child from a parent creating a new parent
1906         * child relationship.  As a consequence the relationships between the 
1907         * old ancestors of the child and its descendants change.  A descendant is
1908         *   
1909         */
1910    
1911        /**
1912         * Recursively modifies the distinguished name of an entry and the names of
1913         * its descendants calling itself in the recursion.
1914         *
1915         * @param id the primary key of the entry
1916         * @param updn User provided distinguished name to set as the new DN
1917         * @param isMove whether or not the name change is due to a move operation
1918         * which affects alias userIndices.
1919         * @throws Exception if something goes wrong
1920         */
1921        private void modifyDn( Long id, DN updn, boolean isMove ) throws Exception
1922        {
1923            String aliasTarget;
1924    
1925            // update normalized DN index
1926            ndnIdx.drop( id );
1927    
1928            if ( !updn.isNormalized() )
1929            {
1930                updn.normalize( schemaManager.getNormalizerMapping() );
1931            }
1932    
1933            ndnIdx.add( updn.getNormName(), id );
1934    
1935            // update user provided DN index
1936            updnIdx.drop( id );
1937            updnIdx.add( updn.getName(), id );
1938    
1939            /* 
1940             * Read Alias Index Tuples
1941             * 
1942             * If this is a name change due to a move operation then the one and
1943             * subtree userIndices for aliases were purged before the aliases were
1944             * moved.  Now we must add them for each alias entry we have moved.  
1945             * 
1946             * aliasTarget is used as a marker to tell us if we're moving an 
1947             * alias.  If it is null then the moved entry is not an alias.
1948             */
1949            if ( isMove )
1950            {
1951                aliasTarget = aliasIdx.reverseLookup( id );
1952    
1953                if ( null != aliasTarget )
1954                {
1955                    addAliasIndices( id, new DN( getEntryDn( id ) ), aliasTarget );
1956                }
1957            }
1958    
1959            Cursor<IndexEntry<Long, E, Long>> children = list( id );
1960    
1961            while ( children.next() )
1962            {
1963                // Get the child and its id
1964                IndexEntry<Long, E, Long> rec = children.get();
1965                Long childId = rec.getId();
1966    
1967                /* 
1968                 * Calculate the DN for the child's new name by copying the parents
1969                 * new name and adding the child's old upRdn to new name as its RDN
1970                 */
1971                DN childUpdn = ( DN ) updn.clone();
1972                DN oldUpdn = new DN( getEntryUpdn( childId ) );
1973    
1974                String rdn = oldUpdn.get( oldUpdn.size() - 1 );
1975                DN rdnDN = new DN( rdn );
1976                rdnDN.normalize( schemaManager.getNormalizerMapping() );
1977                childUpdn.add( rdnDN.getRdn() );
1978    
1979                // Modify the child
1980                ServerEntry entry = lookup( childId );
1981                entry.setDn( childUpdn );
1982                master.put( childId, entry );
1983    
1984                // Recursively change the names of the children below
1985                modifyDn( childId, childUpdn, isMove );
1986            }
1987    
1988            children.close();
1989        }
1990    
1991    
1992        public void move( DN oldChildDn, DN newParentDn, RDN newRdn, boolean deleteOldRdn ) throws Exception
1993        {
1994            Long childId = getEntryId( oldChildDn.getNormName() );
1995            rename( oldChildDn, newRdn, deleteOldRdn );
1996            DN newUpdn = move( oldChildDn, childId, newParentDn );
1997    
1998            // Update the current entry
1999            ServerEntry entry = lookup( childId );
2000            entry.setDn( newUpdn );
2001            master.put( childId, entry );
2002    
2003            if ( isSyncOnWrite )
2004            {
2005                sync();
2006            }
2007        }
2008    
2009    
2010        public void move( DN oldChildDn, DN newParentDn ) throws Exception
2011        {
2012            Long childId = getEntryId( oldChildDn.getNormName() );
2013            DN newUpdn = move( oldChildDn, childId, newParentDn );
2014    
2015            // Update the current entry
2016            ServerEntry entry = lookup( childId );
2017            entry.setDn( newUpdn );
2018            master.put( childId, entry );
2019    
2020            if ( isSyncOnWrite )
2021            {
2022                sync();
2023            }
2024        }
2025    
2026    
2027        /**
2028         * Moves an entry under a new parent.  The operation causes a shift in the
2029         * parent child relationships between the old parent, new parent and the 
2030         * child moved.  All other descendant entries under the child never change
2031         * their direct parent child relationships.  Hence after the parent child
2032         * relationship changes are broken at the old parent and set at the new
2033         * parent a modifyDn operation is conducted to handle name changes 
2034         * propagating down through the moved child and its descendants.
2035         * 
2036         * @param oldChildDn the normalized dn of the child to be moved
2037         * @param childId the id of the child being moved
2038         * @param newParentDn the normalized dn of the new parent for the child
2039         * @throws Exception if something goes wrong
2040         */
2041        private DN move( DN oldChildDn, Long childId, DN newParentDn ) throws Exception
2042        {
2043            // Get the child and the new parent to be entries and Ids
2044            Long newParentId = getEntryId( newParentDn.getNormName() );
2045            Long oldParentId = getParentId( childId );
2046    
2047            /*
2048             * All aliases including and below oldChildDn, will be affected by
2049             * the move operation with respect to one and subtree userIndices since
2050             * their relationship to ancestors above oldChildDn will be 
2051             * destroyed.  For each alias below and including oldChildDn we will
2052             * drop the index tuples mapping ancestor ids above oldChildDn to the
2053             * respective target ids of the aliases.
2054             */
2055            dropMovedAliasIndices( oldChildDn );
2056    
2057            /*
2058             * Drop the old parent child relationship and add the new one
2059             * Set the new parent id for the child replacing the old parent id
2060             */
2061            oneLevelIdx.drop( oldParentId, childId );
2062            oneLevelIdx.add( newParentId, childId );
2063    
2064            updateSubLevelIndex( childId, oldParentId, newParentId );
2065    
2066            /*
2067             * Build the new user provided DN (updn) for the child using the child's
2068             * user provided RDN & the new parent's UPDN.  Basically add the child's
2069             * UpRdn String to the tail of the new parent's Updn Name.
2070             */
2071            DN childUpdn = new DN( getEntryUpdn( childId ) );
2072            String childRdn = childUpdn.get( childUpdn.size() - 1 );
2073            DN newUpdn = new DN( getEntryUpdn( newParentId ) );
2074            newUpdn.add( newUpdn.size(), childRdn );
2075    
2076            // Call the modifyDn operation with the new updn
2077            modifyDn( childId, newUpdn, true );
2078    
2079            return newUpdn;
2080        }
2081    
2082    
2083        /**
2084         * 
2085         * updates the SubLevel Index as part of a move operation.
2086         *
2087         * @param childId child id to be moved
2088         * @param oldParentId old parent's id
2089         * @param newParentId new parent's id
2090         * @throws Exception
2091         */
2092        private void updateSubLevelIndex( Long childId, Long oldParentId, Long newParentId ) throws Exception
2093        {
2094            Long tempId = oldParentId;
2095            List<Long> parentIds = new ArrayList<Long>();
2096    
2097            // find all the parents of the oldParentId
2098            while ( tempId != 0 && tempId != 1 && tempId != null )
2099            {
2100                parentIds.add( tempId );
2101                tempId = getParentId( tempId );
2102            }
2103    
2104            // find all the children of the childId
2105            Cursor<IndexEntry<Long, E, Long>> cursor = subLevelIdx.forwardCursor( childId );
2106    
2107            List<Long> childIds = new ArrayList<Long>();
2108            childIds.add( childId );
2109    
2110            while ( cursor.next() )
2111            {
2112                childIds.add( cursor.get().getId() );
2113            }
2114    
2115            // detach the childId and all its children from oldParentId and all it parents excluding the root
2116            for ( Long pid : parentIds )
2117            {
2118                for ( Long cid : childIds )
2119                {
2120                    subLevelIdx.drop( pid, cid );
2121                }
2122            }
2123    
2124            parentIds.clear();
2125            tempId = newParentId;
2126    
2127            // find all the parents of the newParentId
2128            while ( tempId != 0 && tempId != 1 && tempId != null )
2129            {
2130                parentIds.add( tempId );
2131                tempId = getParentId( tempId );
2132            }
2133    
2134            // attach the childId and all its children to newParentId and all it parents excluding the root
2135            for ( Long id : parentIds )
2136            {
2137                for ( Long cid : childIds )
2138                {
2139                    subLevelIdx.add( id, cid );
2140                }
2141            }
2142        }
2143    
2144    
2145        /**
2146         * For all aliases including and under the moved base, this method removes
2147         * one and subtree alias index tuples for old ancestors above the moved base
2148         * that will no longer be ancestors after the move.
2149         * 
2150         * @param movedBase the base at which the move occured - the moved node
2151         * @throws Exception if system userIndices fail
2152         */
2153        private void dropMovedAliasIndices( final DN movedBase ) throws Exception
2154        {
2155            //        // Find all the aliases from movedBase down
2156            //        IndexAssertion<Object,E> isBaseDescendant = new IndexAssertion<Object,E>()
2157            //        {
2158            //            public boolean assertCandidate( IndexEntry<Object,E> rec ) throws Exception
2159            //            {
2160            //                String dn = getEntryDn( rec.getId() );
2161            //                return dn.endsWith( movedBase.toString() );
2162            //            }
2163            //        };
2164    
2165            Long movedBaseId = getEntryId( movedBase.getNormName() );
2166    
2167            if ( aliasIdx.reverseLookup( movedBaseId ) != null )
2168            {
2169                dropAliasIndices( movedBaseId, movedBase );
2170            }
2171    
2172            //        throw new NotImplementedException( "Fix the code below this line" );
2173    
2174            //        NamingEnumeration<ForwardIndexEntry> aliases =
2175            //                new IndexAssertionEnumeration( aliasIdx.listIndices( movedBase.toString(), true ), isBaseDescendant );
2176            //
2177            //        while ( aliases.hasMore() )
2178            //        {
2179            //            ForwardIndexEntry entry = aliases.next();
2180            //            dropAliasIndices( (Long)entry.getId(), movedBase );
2181            //        }
2182        }
2183    
2184    
2185        /**
2186         * For the alias id all ancestor one and subtree alias tuples are moved 
2187         * above the moved base.
2188         * 
2189         * @param aliasId the id of the alias 
2190         * @param movedBase the base where the move occured
2191         * @throws Exception if userIndices fail
2192         */
2193        private void dropAliasIndices( Long aliasId, DN movedBase ) throws Exception
2194        {
2195            String targetDn = aliasIdx.reverseLookup( aliasId );
2196            Long targetId = getEntryId( targetDn );
2197            String aliasDn = getEntryDn( aliasId );
2198    
2199            /*
2200             * Start droping index tuples with the first ancestor right above the 
2201             * moved base.  This is the first ancestor effected by the move.
2202             */
2203            DN ancestorDn = ( DN ) movedBase.getPrefix( 1 );
2204            Long ancestorId = getEntryId( ancestorDn.getNormName() );
2205    
2206            /*
2207             * We cannot just drop all tuples in the one level and subtree userIndices
2208             * linking baseIds to the targetId.  If more than one alias refers to
2209             * the target then droping all tuples with a value of targetId would
2210             * make all other aliases to the target inconsistent.
2211             * 
2212             * We need to walk up the path of alias ancestors right above the moved 
2213             * base until we reach the upSuffix, deleting each ( ancestorId,
2214             * targetId ) tuple in the subtree scope alias.  We only need to do 
2215             * this for the direct parent of the alias on the one level subtree if
2216             * the moved base is the alias.
2217             */
2218            if ( aliasDn.equals( movedBase.toString() ) )
2219            {
2220                oneAliasIdx.drop( ancestorId, targetId );
2221            }
2222    
2223            subAliasIdx.drop( ancestorId, targetId );
2224    
2225            while ( !ancestorDn.equals( upSuffix ) )
2226            {
2227                ancestorDn = ( DN ) ancestorDn.getPrefix( 1 );
2228                ancestorId = getEntryId( ancestorDn.getNormName() );
2229    
2230                subAliasIdx.drop( ancestorId, targetId );
2231            }
2232        }
2233    
2234    
2235        /**
2236         * @param schemaManager the schemaManager to set
2237         */
2238        public void setSchemaManager( SchemaManager schemaManager )
2239        {
2240            this.schemaManager = schemaManager;
2241        }
2242    }