View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.core.partition.impl.btree;
21  
22  
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.net.URI;
26  import java.time.Duration;
27  import java.util.Arrays;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.concurrent.Semaphore;
35  import java.util.concurrent.atomic.AtomicBoolean;
36  import java.util.concurrent.locks.ReadWriteLock;
37  import java.util.concurrent.locks.ReentrantReadWriteLock;
38  
39  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
40  import org.apache.directory.api.ldap.model.cursor.Cursor;
41  import org.apache.directory.api.ldap.model.cursor.CursorException;
42  import org.apache.directory.api.ldap.model.entry.Attribute;
43  import org.apache.directory.api.ldap.model.entry.Entry;
44  import org.apache.directory.api.ldap.model.entry.Modification;
45  import org.apache.directory.api.ldap.model.entry.Value;
46  import org.apache.directory.api.ldap.model.exception.LdapAliasDereferencingException;
47  import org.apache.directory.api.ldap.model.exception.LdapAliasException;
48  import org.apache.directory.api.ldap.model.exception.LdapContextNotEmptyException;
49  import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException;
50  import org.apache.directory.api.ldap.model.exception.LdapException;
51  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
52  import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException;
53  import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException;
54  import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
55  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
56  import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException;
57  import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
58  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
59  import org.apache.directory.api.ldap.model.name.Ava;
60  import org.apache.directory.api.ldap.model.name.Dn;
61  import org.apache.directory.api.ldap.model.name.Rdn;
62  import org.apache.directory.api.ldap.model.schema.AttributeType;
63  import org.apache.directory.api.ldap.model.schema.MatchingRule;
64  import org.apache.directory.api.ldap.model.schema.Normalizer;
65  import org.apache.directory.api.ldap.model.schema.SchemaManager;
66  import org.apache.directory.api.util.Strings;
67  import org.apache.directory.api.util.exception.MultiException;
68  import org.apache.directory.server.constants.ApacheSchemaConstants;
69  import org.apache.directory.server.core.api.DnFactory;
70  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
71  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
72  import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
73  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
74  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
75  import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
76  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
77  import org.apache.directory.server.core.api.interceptor.context.ModDnAva;
78  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
79  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
80  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
81  import org.apache.directory.server.core.api.interceptor.context.OperationContext;
82  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
83  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
84  import org.apache.directory.server.core.api.interceptor.context.UnbindOperationContext;
85  import org.apache.directory.server.core.api.partition.AbstractPartition;
86  import org.apache.directory.server.core.api.partition.Partition;
87  import org.apache.directory.server.core.api.partition.PartitionTxn;
88  import org.apache.directory.server.core.api.partition.PartitionWriteTxn;
89  import org.apache.directory.server.core.api.partition.Subordinates;
90  import org.apache.directory.server.i18n.I18n;
91  import org.apache.directory.server.xdbm.Index;
92  import org.apache.directory.server.xdbm.IndexEntry;
93  import org.apache.directory.server.xdbm.IndexNotFoundException;
94  import org.apache.directory.server.xdbm.MasterTable;
95  import org.apache.directory.server.xdbm.ParentIdAndRdn;
96  import org.apache.directory.server.xdbm.Store;
97  import org.apache.directory.server.xdbm.search.Optimizer;
98  import org.apache.directory.server.xdbm.search.PartitionSearchResult;
99  import org.apache.directory.server.xdbm.search.SearchEngine;
100 import org.slf4j.Logger;
101 import org.slf4j.LoggerFactory;
102 
103 import com.github.benmanes.caffeine.cache.Cache;
104 import com.github.benmanes.caffeine.cache.Caffeine;
105 
106 
107 /**
108  * An abstract {@link Partition} that uses general BTree operations.
109  *
110  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
111  */
112 public abstract class AbstractBTreePartition extends AbstractPartition implements Store
113 {
114     /** static logger */
115     private static final Logger LOG = LoggerFactory.getLogger( AbstractBTreePartition.class );
116 
117     /** the search engine used to search the database */
118     private SearchEngine searchEngine;
119 
120     /** The optimizer to use during search operation */
121     private Optimizer optimizer;
122 
123     /** Tells if the Optimizer is enabled */
124     protected boolean optimizerEnabled = true;
125 
126     /** The default cache size is set to 10 000 objects */
127     public static final int DEFAULT_CACHE_SIZE = 10000;
128 
129     /** The Entry cache size for this partition */
130     protected int cacheSize = DEFAULT_CACHE_SIZE;
131 
132     /** The alias cache */
133     protected Cache<String, Dn> aliasCache;
134 
135     /** The ParentIdAndRdn cache */
136     protected Cache<String, ParentIdAndRdn> piarCache;
137 
138     /** true if we sync disks on every write operation */
139     protected AtomicBoolean isSyncOnWrite = new AtomicBoolean( true );
140 
141     /** The suffix UUID */
142     private volatile String suffixId;
143 
144     /** The path in which this Partition stores files */
145     protected URI partitionPath;
146 
147     /** The set of indexed attributes */
148     private Set<Index<?, String>> indexedAttributes;
149 
150     /** the master table storing entries by primary key */
151     protected MasterTable master;
152 
153     /** a map of attributeType numeric UUID to user userIndices */
154     protected Map<String, Index<?, String>> userIndices = new HashMap<>();
155 
156     /** a map of attributeType numeric UUID to system userIndices */
157     protected Map<String, Index<?, String>> systemIndices = new HashMap<>();
158 
159     /** the relative distinguished name index */
160     protected Index<ParentIdAndRdn, String> rdnIdx;
161 
162     /** a system index on objectClass attribute*/
163     protected Index<String, String> objectClassIdx;
164 
165     /** the attribute presence index */
166     protected Index<String, String> presenceIdx;
167 
168     /** a system index on entryCSN attribute */
169     protected Index<String, String> entryCsnIdx;
170 
171     /** a system index on aliasedObjectName attribute */
172     protected Index<Dn, String> aliasIdx;
173 
174     /** the subtree scope alias index */
175     protected Index<String, String> subAliasIdx;
176 
177     /** the one level scope alias index */
178     protected Index<String, String> oneAliasIdx;
179 
180     /** a system index on administrativeRole attribute */
181     protected Index<String, String> adminRoleIdx;
182 
183     /** Cached attributes types to avoid lookup all over the code */
184     protected AttributeType objectClassAT;
185     private Normalizer objectClassNormalizer;
186     protected AttributeType presenceAT;
187     private Normalizer presenceNormalizer;
188     protected AttributeType entryCsnAT;
189     protected AttributeType entryDnAT;
190     protected AttributeType entryUuidAT;
191     protected AttributeType aliasedObjectNameAT;
192     protected AttributeType administrativeRoleAT;
193     protected AttributeType contextCsnAT;
194     
195     /** Cached value for TOP */
196     private Value topOCValue;
197 
198     private static final boolean NO_REVERSE = Boolean.FALSE;
199     private static final boolean WITH_REVERSE = Boolean.TRUE;
200     
201     private static final boolean ADD_CACHE = Boolean.TRUE;
202     private static final boolean DEL_CACHE = Boolean.FALSE;
203 
204     protected static final boolean ADD_CHILD = true;
205     protected static final boolean REMOVE_CHILD = false;
206 
207     /** A lock to protect the backend from concurrent reads/writes */
208     private ReadWriteLock rwLock;
209 
210     /** a cache to hold <entryUUID, Dn> pairs, this is used for speeding up the buildEntryDn() method */
211     private Cache<String, Dn> entryDnCache;
212     
213     /** a semaphore to serialize the writes on context entry while updating contextCSN attribute */
214     private Semaphore ctxCsnSemaphore = new Semaphore( 1 );
215     
216     // ------------------------------------------------------------------------
217     // C O N S T R U C T O R S
218     // ------------------------------------------------------------------------
219 
220     /**
221      * Creates a B-tree based context partition.
222      * 
223      * @param schemaManager the schema manager
224      */
225     protected AbstractBTreePartition( SchemaManager schemaManager )
226     {
227         this.schemaManager = schemaManager;
228 
229         initInstance();
230     }
231 
232 
233     /**
234      * Creates a B-tree based context partition.
235      * 
236      * @param schemaManager the schema manager
237      * @param dnFactory the DN factory
238      */
239     protected AbstractBTreePartition( SchemaManager schemaManager, DnFactory dnFactory )
240     {
241         this.schemaManager = schemaManager;
242         this.dnFactory = dnFactory;
243 
244         initInstance();
245     }
246 
247 
248     /**
249      * Intializes the instance.
250      */
251     private void initInstance()
252     {
253         indexedAttributes = new HashSet<>();
254 
255         // Initialize Attribute types used all over this method
256         objectClassAT = schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT );
257         objectClassNormalizer = objectClassAT.getEquality().getNormalizer();
258         presenceAT = schemaManager.getAttributeType( ApacheSchemaConstants.APACHE_PRESENCE_AT );
259         presenceNormalizer = presenceAT.getEquality().getNormalizer();
260         aliasedObjectNameAT = schemaManager.getAttributeType( SchemaConstants.ALIASED_OBJECT_NAME_AT );
261         entryCsnAT = schemaManager.getAttributeType( SchemaConstants.ENTRY_CSN_AT );
262         entryDnAT = schemaManager.getAttributeType( SchemaConstants.ENTRY_DN_AT );
263         entryUuidAT = schemaManager.getAttributeType( SchemaConstants.ENTRY_UUID_AT );
264         administrativeRoleAT = schemaManager.getAttributeType( SchemaConstants.ADMINISTRATIVE_ROLE_AT );
265         contextCsnAT = schemaManager.getAttributeType( SchemaConstants.CONTEXT_CSN_AT );
266         
267         // Initialize a Value for TOP_OC
268         try
269         {
270             topOCValue = new Value( schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT_OID ), SchemaConstants.TOP_OC_OID );
271         }
272         catch ( LdapInvalidAttributeValueException e )
273         {
274             // There is nothing we can do...
275         }
276         
277         // Relax the entryDnAT so that we don't check the EntryDN twice
278         entryDnAT.setRelaxed( true );
279     }
280 
281 
282     // ------------------------------------------------------------------------
283     // C O N F I G U R A T I O N   M E T H O D S
284     // ------------------------------------------------------------------------
285     /**
286      * Gets the entry cache size for this BTreePartition.
287      *
288      * @return the maximum size of the cache as the number of entries maximum before paging out
289      */
290     @Override
291     public int getCacheSize()
292     {
293         return cacheSize;
294     }
295 
296 
297     /**
298      * Used to specify the entry cache size for a Partition.  Various Partition
299      * implementations may interpret this value in different ways: i.e. total cache
300      * size limit verses the number of entries to cache.
301      *
302      * @param cacheSize the maximum size of the cache in the number of entries
303      */
304     @Override
305     public void setCacheSize( int cacheSize )
306     {
307         this.cacheSize = cacheSize;
308     }
309 
310 
311     /**
312      * Tells if the Optimizer is enabled or not
313      * @return true if the optimizer is enabled
314      */
315     public boolean isOptimizerEnabled()
316     {
317         return optimizerEnabled;
318     }
319 
320 
321     /**
322      * Set the optimizer flag
323      * @param optimizerEnabled The flag
324      */
325     public void setOptimizerEnabled( boolean optimizerEnabled )
326     {
327         this.optimizerEnabled = optimizerEnabled;
328     }
329 
330 
331     /**
332      * Sets the path in which this Partition stores data. This may be an URL to
333      * a file or directory, or an JDBC URL.
334      *
335      * @param partitionPath the path in which this Partition stores data.
336      */
337     @Override
338     public void setPartitionPath( URI partitionPath )
339     {
340         checkInitialized( "partitionPath" );
341         this.partitionPath = partitionPath;
342     }
343 
344 
345     /**
346      * {@inheritDoc}
347      */
348     @Override
349     public boolean isSyncOnWrite()
350     {
351         return isSyncOnWrite.get();
352     }
353 
354 
355     /**
356      * {@inheritDoc}
357      */
358     @Override
359     public void setSyncOnWrite( boolean isSyncOnWrite )
360     {
361         checkInitialized( "syncOnWrite" );
362         this.isSyncOnWrite.set( isSyncOnWrite );
363     }
364 
365 
366     /**
367      * Sets up the system indices.
368      * 
369      * @throws LdapException If the setup failed
370      */
371     @SuppressWarnings("unchecked")
372     protected void setupSystemIndices() throws LdapException
373     {
374         // add missing system indices
375         if ( getPresenceIndex() == null )
376         {
377             Index<String, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_PRESENCE_AT_OID,
378                 partitionPath, NO_REVERSE );
379             addIndex( index );
380         }
381 
382         if ( getRdnIndex() == null )
383         {
384             Index<ParentIdAndRdn, String> index = createSystemIndex(
385                 ApacheSchemaConstants.APACHE_RDN_AT_OID,
386                 partitionPath, WITH_REVERSE );
387             addIndex( index );
388         }
389 
390         if ( getAliasIndex() == null )
391         {
392             Index<Dn, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_ALIAS_AT_OID,
393                 partitionPath, WITH_REVERSE );
394             addIndex( index );
395         }
396 
397         if ( getOneAliasIndex() == null )
398         {
399             Index<String, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID,
400                 partitionPath, NO_REVERSE );
401             addIndex( index );
402         }
403 
404         if ( getSubAliasIndex() == null )
405         {
406             Index<String, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID,
407                 partitionPath, NO_REVERSE );
408             addIndex( index );
409         }
410 
411         if ( getObjectClassIndex() == null )
412         {
413             Index<String, String> index = createSystemIndex( SchemaConstants.OBJECT_CLASS_AT_OID, partitionPath,
414                 NO_REVERSE );
415             addIndex( index );
416         }
417 
418         if ( getEntryCsnIndex() == null )
419         {
420             Index<String, String> index = createSystemIndex( SchemaConstants.ENTRY_CSN_AT_OID, partitionPath,
421                 NO_REVERSE );
422             addIndex( index );
423         }
424 
425         if ( getAdministrativeRoleIndex() == null )
426         {
427             Index<String, String> index = createSystemIndex( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID,
428                 partitionPath,
429                 NO_REVERSE );
430             addIndex( index );
431         }
432 
433         // convert and initialize system indices
434         for ( Map.Entry<String, Index<?, String>> elem : systemIndices.entrySet() )
435         {
436             Index<?, String> index = elem.getValue();
437             index = convertAndInit( index );
438             systemIndices.put( elem.getKey(), index );
439         }
440 
441         // set index shortcuts
442         rdnIdx = ( Index<ParentIdAndRdn, String> ) systemIndices
443             .get( ApacheSchemaConstants.APACHE_RDN_AT_OID );
444         presenceIdx = ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_PRESENCE_AT_OID );
445         aliasIdx = ( Index<Dn, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_ALIAS_AT_OID );
446         oneAliasIdx = ( Index<String, String> ) systemIndices
447             .get( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID );
448         subAliasIdx = ( Index<String, String> ) systemIndices
449             .get( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID );
450         objectClassIdx = ( Index<String, String> ) systemIndices.get( SchemaConstants.OBJECT_CLASS_AT_OID );
451         entryCsnIdx = ( Index<String, String> ) systemIndices.get( SchemaConstants.ENTRY_CSN_AT_OID );
452         adminRoleIdx = ( Index<String, String> ) systemIndices.get( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID );
453     }
454 
455 
456     /**
457      * Sets up the user indices.
458      * 
459      * @throws LdapException If the setup failed
460      */
461     protected void setupUserIndices() throws LdapException
462     {
463         // convert and initialize system indices
464         Map<String, Index<?, String>> tmp = new HashMap<>();
465 
466         for ( Map.Entry<String, Index<?, String>> elem : userIndices.entrySet() )
467         {
468             String oid = elem.getKey();
469             
470             // check that the attributeType has an EQUALITY matchingRule
471             AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid );
472             MatchingRule mr = attributeType.getEquality();
473 
474             if ( mr != null )
475             {
476                 Index<?, String> index = elem.getValue();
477                 index = convertAndInit( index );
478                 tmp.put( oid, index );
479             }
480             else
481             {
482                 LOG.error( I18n.err( I18n.ERR_4, attributeType.getName() ) );
483             }
484         }
485 
486         userIndices = tmp;
487     }
488 
489 
490     /**
491      * Gets the DefaultSearchEngine used by this ContextPartition to search the
492      * Database.
493      *
494      * @return the search engine
495      */
496     public SearchEngine getSearchEngine()
497     {
498         return searchEngine;
499     }
500 
501 
502     // -----------------------------------------------------------------------
503     // Miscellaneous abstract methods
504     // -----------------------------------------------------------------------
505     /**
506      * Convert and initialize an index for a specific store implementation.
507      *
508      * @param index the index
509      * @return the converted and initialized index
510      * @throws LdapException If teh conversion failed
511      */
512     protected abstract Index<?, String> convertAndInit( Index<?, String> index ) throws LdapException;
513 
514 
515     /**
516      * Gets the path in which this Partition stores data.
517      *
518      * @return the path in which this Partition stores data.
519      */
520     @Override
521     public URI getPartitionPath()
522     {
523         return partitionPath;
524     }
525 
526 
527     // ------------------------------------------------------------------------
528     // Partition Interface Method Implementations
529     // ------------------------------------------------------------------------
530     /**
531      * {@inheritDoc}
532      */
533     @Override
534     protected void doDestroy( PartitionTxn partitionTxn ) throws LdapException
535     {
536         LOG.debug( "destroy() called on store for {}", this.suffixDn );
537 
538         if ( !initialized )
539         {
540             return;
541         }
542 
543         // don't reset initialized flag
544         initialized = false;
545 
546         aliasCache.invalidateAll();
547         piarCache.invalidateAll();
548         entryDnCache.invalidateAll();
549 
550         MultiException errors = new MultiException( I18n.err( I18n.ERR_577 ) );
551 
552         for ( Index<?, String> index : userIndices.values() )
553         {
554             try
555             {
556                 index.close( partitionTxn );
557                 LOG.debug( "Closed {} user index for {} partition.", index.getAttributeId(), suffixDn );
558             }
559             catch ( Throwable t )
560             {
561                 LOG.error( I18n.err( I18n.ERR_124 ), t );
562                 errors.addThrowable( t );
563             }
564         }
565 
566         for ( Index<?, String> index : systemIndices.values() )
567         {
568             try
569             {
570                 index.close( partitionTxn );
571                 LOG.debug( "Closed {} system index for {} partition.", index.getAttributeId(), suffixDn );
572             }
573             catch ( Throwable t )
574             {
575                 LOG.error( I18n.err( I18n.ERR_124 ), t );
576                 errors.addThrowable( t );
577             }
578         }
579 
580         try
581         {
582             master.close( partitionTxn );
583             
584             if ( LOG.isDebugEnabled() )
585             {
586                 LOG.debug( I18n.err( I18n.ERR_125, suffixDn ) );
587             }
588         }
589         catch ( Throwable t )
590         {
591             LOG.error( I18n.err( I18n.ERR_126 ), t );
592             errors.addThrowable( t );
593         }
594 
595         if ( errors.size() > 0 )
596         {
597             throw new LdapOtherException( errors.getMessage(), errors );
598         }
599     }
600 
601 
602     /**
603      * {@inheritDoc}
604      */
605     @Override
606     public void repair() throws LdapException
607     {
608         // Do nothing by default
609         doRepair();
610     }
611 
612 
613     /**
614      * {@inheritDoc}
615      */
616     @Override
617     protected void doInit() throws LdapException
618     {
619         // First, inject the indexed attributes if any
620         if ( ( indexedAttributes != null ) && ( !indexedAttributes.isEmpty() ) )
621         {
622             for ( Index index : indexedAttributes )
623             {
624                 addIndex( index );
625             }
626         }
627 
628         // Now, initialize the configured index
629         setupSystemIndices();
630         setupUserIndices();
631 
632         aliasCache = Caffeine.newBuilder().maximumSize( cacheSize ).expireAfterAccess( Duration.ofMinutes( 20 ) )
633             .build();
634 
635         piarCache = Caffeine.newBuilder().maximumSize( cacheSize * 3L )
636             .expireAfterAccess( Duration.ofMinutes( 20 ) ).build();
637 
638         entryDnCache = Caffeine.newBuilder().maximumSize( cacheSize ).expireAfterAccess( Duration.ofMinutes( 20 ) )
639             .build();
640     }
641 
642 
643     private void dumpAllRdnIdx( PartitionTxn partitionTxn ) throws LdapException, CursorException, IOException
644     {
645         if ( LOG.isDebugEnabled() )
646         {
647             dumpRdnIdx( partitionTxn, Partition.ROOT_ID, "" );
648             System.out.println( "-----------------------------" );
649         }
650     }
651 
652 
653     private void dumpRdnIdx( PartitionTxn partitionTxn ) throws LdapException, CursorException, IOException
654     {
655         if ( LOG.isDebugEnabled() )
656         {
657             dumpRdnIdx( partitionTxn, Partition.ROOT_ID, 1, "" );
658             System.out.println( "-----------------------------" );
659         }
660     }
661 
662 
663     /**
664      * Dump the RDN index content
665      *  
666      * @param partitionTxn The transaction to use
667      * @param id The root ID
668      * @param tabs The space prefix
669      * @throws LdapException If we had an issue while dumping the Rdn index
670      * @throws CursorException If the cursor failed to browse the Rdn Index
671      * @throws IOException If we weren't able to read teh Rdn Index file
672      */
673     public void dumpRdnIdx( PartitionTxn partitionTxn, String id, String tabs ) throws LdapException, CursorException, IOException
674     {
675         // Start with the root
676         Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = rdnIdx.forwardCursor( partitionTxn );
677 
678         IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
679         startingPos.setKey( new ParentIdAndRdn( id, ( Rdn[] ) null ) );
680         cursor.before( startingPos );
681 
682         while ( cursor.next() )
683         {
684             IndexEntry<ParentIdAndRdn, String> entry = cursor.get();
685             System.out.println( tabs + entry );
686         }
687 
688         cursor.close();
689     }
690 
691 
692     private void dumpRdnIdx( PartitionTxn partitionTxn, String id, int nbSibbling, String tabs ) 
693         throws LdapException, CursorException, IOException
694     {
695         // Start with the root
696         Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = rdnIdx.forwardCursor( partitionTxn );
697 
698         IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>();
699         startingPos.setKey( new ParentIdAndRdn( id, ( Rdn[] ) null ) );
700         cursor.before( startingPos );
701         int countChildren = 0;
702 
703         while ( cursor.next() && ( countChildren < nbSibbling ) )
704         {
705             IndexEntry<ParentIdAndRdn, String> entry = cursor.get();
706             System.out.println( tabs + entry );
707             countChildren++;
708 
709             // And now, the children
710             int nbChildren = entry.getKey().getNbChildren();
711 
712             if ( nbChildren > 0 )
713             {
714                 dumpRdnIdx( partitionTxn, entry.getId(), nbChildren, tabs + "  " );
715             }
716         }
717 
718         cursor.close();
719     }
720 
721 
722     //---------------------------------------------------------------------------------------------
723     // The Add operation
724     //---------------------------------------------------------------------------------------------
725     private ParentIdAndRdn getParentId( PartitionTxn partitionTxn, Dn entryDn ) throws LdapException
726     {
727         ParentIdAndRdn key;
728 
729         if ( entryDn.getNormName().equals( suffixDn.getNormName() ) )
730         {
731             key = new ParentIdAndRdn( Partition.ROOT_ID, suffixDn.getRdns() );
732         }
733         else
734         {
735             String parentId = null;
736             Dn parentDn = entryDn.getParent();
737 
738             lockRead();
739 
740             try
741             {
742                 parentId = getEntryId( partitionTxn, parentDn );
743             }
744             finally
745             {
746                 unlockRead();
747             }
748 
749             if ( parentId == null )
750             {
751                 return null;
752             }
753             
754             key = new ParentIdAndRdn( parentId, entryDn.getRdn() );
755         }
756         
757         return key;
758     }
759     
760     
761     /**
762      * {@inheritDoc}
763      */
764     @Override
765     public void add( AddOperationContext addContext ) throws LdapException
766     {
767         PartitionTxn partitionTxn = addContext.getTransaction();
768         
769         assert ( partitionTxn != null );
770         assert ( partitionTxn instanceof PartitionWriteTxn );
771 
772         try
773         {
774             setRWLock( addContext );
775             Entry entry = ( ( ClonedServerEntry ) addContext.getEntry() ).getClonedEntry();
776 
777             Dn entryDn = entry.getDn();
778 
779             // check if the entry already exists
780             ParentIdAndRdn parentIdAndRdn = getParentId( partitionTxn, entryDn );
781             
782             // don't keep going if we cannot find the parent Id
783             if ( parentIdAndRdn == null )
784             {
785                 throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_216_ID_FOR_PARENT_NOT_FOUND, 
786                     parentIdAndRdn ) );
787             }
788 
789             String parentId = parentIdAndRdn.getParentId();
790 
791             lockRead();
792 
793             try
794             {
795                 if ( rdnIdx.forwardLookup( partitionTxn, parentIdAndRdn ) != null )
796                 {
797                     throw new LdapEntryAlreadyExistsException(
798                         I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, entryDn.getName() ) );
799                 }
800             }
801             finally
802             {
803                 unlockRead();
804             }
805 
806             // Get a new UUID for the added entry if it does not have any already
807             Attribute entryUUID = entry.get( entryUuidAT );
808 
809             String id;
810 
811             if ( entryUUID == null )
812             {
813                 id = master.getNextId( entry );
814             }
815             else
816             {
817                 id = entryUUID.getString();
818             }
819             
820             if ( entryDn.getNormName().equals( suffixDn.getNormName() ) )
821             {
822                 suffixId = id;
823             }
824 
825             // Update the ObjectClass index
826             Attribute objectClass = entry.get( objectClassAT );
827 
828             if ( objectClass == null )
829             {
830                 String msg = I18n.err( I18n.ERR_217, entryDn.getName(), entry );
831                 ResultCodeEnum rc = ResultCodeEnum.OBJECT_CLASS_VIOLATION;
832                 
833                 throw new LdapSchemaViolationException( rc, msg );
834             }
835 
836             for ( Value value : objectClass )
837             {
838                 if ( value.equals( topOCValue ) )
839                 {
840                     continue;
841                 }
842                 
843                 String normalizedOc = objectClassNormalizer.normalize( value.getString() );
844 
845                 objectClassIdx.add( partitionTxn, normalizedOc, id );
846             }
847 
848             if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
849             {
850                 Attribute aliasAttr = entry.get( aliasedObjectNameAT );
851                 
852                 addAliasIndices( partitionTxn, id, entryDn, new Dn( schemaManager, aliasAttr.getString() ) );
853             }
854 
855             // Update the EntryCsn index
856             Attribute entryCsn = entry.get( entryCsnAT );
857 
858             if ( entryCsn == null )
859             {
860                 String msg = I18n.err( I18n.ERR_219, entryDn.getName(), entry );
861                 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg );
862             }
863 
864             entryCsnIdx.add( partitionTxn, entryCsn.getString(), id );
865 
866             // Update the AdministrativeRole index, if needed
867             if ( entry.containsAttribute( administrativeRoleAT ) )
868             {
869                 // We may have more than one role
870                 Attribute adminRoles = entry.get( administrativeRoleAT );
871 
872                 for ( Value value : adminRoles )
873                 {
874                     adminRoleIdx.add( partitionTxn, value.getString(), id );
875                 }
876 
877                 // Adds only those attributes that are indexed
878                 presenceIdx.add( partitionTxn, administrativeRoleAT.getOid(), id );
879             }
880 
881             // Now work on the user defined userIndices
882             for ( Attribute attribute : entry )
883             {
884                 AttributeType attributeType = attribute.getAttributeType();
885                 String attributeOid = attributeType.getOid();
886 
887                 if ( hasUserIndexOn( attributeType ) )
888                 {
889                     Index<Object, String> userIndex = ( Index<Object, String> ) getUserIndex( attributeType );
890 
891                     // here lookup by attributeId is OK since we got attributeId from
892                     // the entry via the enumeration - it's in there as is for sure
893 
894                     for ( Value value : attribute )
895                     {
896                         String normalized = value.getNormalized();
897                         userIndex.add( partitionTxn, normalized, id );
898                     }
899 
900                     // Adds only those attributes that are indexed
901                     presenceIdx.add( partitionTxn, attributeOid, id );
902                 }
903             }
904 
905             // Add the parentId in the entry
906             entry.put( ApacheSchemaConstants.ENTRY_PARENT_ID_AT, parentId );
907 
908             lockWrite();
909 
910             try
911             {
912                 // Update the RDN index
913                 rdnIdx.add( partitionTxn, parentIdAndRdn, id );
914                 
915                 // Update the PIAR cache at the same time
916                 updatePiarCache( parentIdAndRdn, id, ADD_CACHE );
917 
918                 // Update the parent's nbChildren and nbDescendants values
919                 if ( parentId != Partition.ROOT_ID )
920                 {
921                     updateRdnIdx( partitionTxn, parentId, ADD_CHILD, 0 );
922                 }
923 
924                 // Remove the EntryDN attribute
925                 entry.removeAttributes( entryDnAT );
926 
927                 Attribute at = entry.get( SchemaConstants.ENTRY_CSN_AT );
928                 setContextCsn( at.getString() );
929 
930                 // And finally add the entry into the master table
931                 master.put( partitionTxn, id, entry );
932             }
933             finally
934             {
935                 unlockWrite();
936             }
937         }
938         catch ( LdapException le )
939         {
940             throw le;
941         }
942         catch ( Exception e )
943         {
944             throw new LdapException( e );
945         }
946     }
947 
948 
949     //---------------------------------------------------------------------------------------------
950     // The Delete operation
951     //---------------------------------------------------------------------------------------------
952     /**
953      * {@inheritDoc}
954      */
955     @Override
956     public Entry delete( DeleteOperationContext deleteContext ) throws LdapException
957     {
958         PartitionTxn partitionTxn = deleteContext.getTransaction();
959         
960         assert ( partitionTxn != null );
961         assert ( partitionTxn instanceof PartitionWriteTxn );
962 
963         setRWLock( deleteContext );
964         Dn dn = deleteContext.getDn();
965         String id = null;
966 
967         lockRead();
968 
969         try
970         {
971             id = getEntryId( partitionTxn, dn );
972         }
973         finally
974         {
975             unlockRead();
976         }
977 
978         // don't continue if id is null
979         if ( id == null )
980         {
981             throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_699, dn ) );
982         }
983 
984         long childCount = getChildCount( partitionTxn, id );
985 
986         if ( childCount > 0 )
987         {
988             throw new LdapContextNotEmptyException( I18n.err( I18n.ERR_700, dn ) );
989         }
990 
991         // We now defer the deletion to the implementing class
992         Entry deletedEntry = delete( partitionTxn, id );
993 
994         updateCache( deleteContext );
995         
996         return deletedEntry;
997     }
998 
999 
1000     protected void updateRdnIdx( PartitionTxn partitionTxn, String parentId, boolean addRemove, int nbDescendant ) throws LdapException
1001     {
1002         boolean isFirst = true;
1003 
1004         if ( parentId.equals( Partition.ROOT_ID ) )
1005         {
1006             return;
1007         }
1008 
1009         ParentIdAndRdn parent = rdnIdx.reverseLookup( partitionTxn, parentId );
1010 
1011         while ( parent != null )
1012         {
1013             rdnIdx.drop( partitionTxn, parentId );
1014             
1015             if ( isFirst )
1016             {
1017                 if ( addRemove == ADD_CHILD )
1018                 {
1019                     parent.setNbChildren( parent.getNbChildren() + 1 );
1020                 }
1021                 else
1022                 {
1023                     parent.setNbChildren( parent.getNbChildren() - 1 );
1024                 }
1025 
1026                 isFirst = false;
1027             }
1028 
1029             if ( addRemove == ADD_CHILD )
1030             {
1031                 parent.setNbDescendants( parent.getNbDescendants() + ( nbDescendant + 1 ) );
1032             }
1033             else
1034             {
1035                 parent.setNbDescendants( parent.getNbDescendants() - ( nbDescendant + 1 ) );
1036             }
1037 
1038             // Inject the modified element into the index
1039             rdnIdx.add( partitionTxn, parent, parentId );
1040 
1041             ////dumpRdnIdx();
1042 
1043             parentId = parent.getParentId();
1044             parent = rdnIdx.reverseLookup( partitionTxn, parentId );
1045         }
1046     }
1047 
1048 
1049     /**
1050      * Delete the entry associated with a given Id
1051      * 
1052      * @param partitionTxn The transaction to use
1053      * @param id The id of the entry to delete
1054      * @return the deleted entry if found
1055      * @throws LdapException If the deletion failed
1056      */
1057     @Override
1058     public Entry delete( PartitionTxn partitionTxn, String id ) throws LdapException
1059     {
1060         try
1061         {
1062             // First get the entry
1063             Entry entry = null;
1064 
1065             lockRead();
1066 
1067             try
1068             {
1069                  entry = master.get( partitionTxn, id );
1070             }
1071             finally
1072             {
1073                 unlockRead();
1074             }
1075 
1076             if ( entry == null )
1077             {
1078                 // Not allowed
1079                 throw new LdapNoSuchObjectException( "Cannot find an entry for UUID " + id );
1080             }
1081 
1082             Attribute objectClass = entry.get( objectClassAT );
1083 
1084             if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
1085             {
1086                 dropAliasIndices( partitionTxn, id );
1087             }
1088 
1089             // Update the ObjectClass index
1090             for ( Value value : objectClass )
1091             {
1092                 if ( value.equals( topOCValue ) )
1093                 {
1094                     continue;
1095                 }
1096                 
1097                 String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1098 
1099                 objectClassIdx.drop( partitionTxn, normalizedOc, id );
1100             }
1101 
1102             // Update the parent's nbChildren and nbDescendants values
1103             ParentIdAndRdn parent = rdnIdx.reverseLookup( partitionTxn, id );
1104             updateRdnIdx( partitionTxn, parent.getParentId(), REMOVE_CHILD, 0 );
1105 
1106             // Update the rdn, oneLevel, subLevel, and entryCsn indexes
1107             entryCsnIdx.drop( partitionTxn, entry.get( entryCsnAT ).getString(), id );
1108 
1109             // Update the AdministrativeRole index, if needed
1110             if ( entry.containsAttribute( administrativeRoleAT ) )
1111             {
1112                 // We may have more than one role
1113                 Attribute adminRoles = entry.get( administrativeRoleAT );
1114 
1115                 for ( Value value : adminRoles )
1116                 {
1117                     adminRoleIdx.drop( partitionTxn, value.getString(), id );
1118                 }
1119 
1120                 // Deletes only those attributes that are indexed
1121                 presenceIdx.drop( partitionTxn, administrativeRoleAT.getOid(), id );
1122             }
1123 
1124             // Update the user indexes
1125             for ( Attribute attribute : entry )
1126             {
1127                 AttributeType attributeType = attribute.getAttributeType();
1128                 String attributeOid = attributeType.getOid();
1129 
1130                 if ( hasUserIndexOn( attributeType ) )
1131                 {
1132                     Index<?, String> userIndex = getUserIndex( attributeType );
1133 
1134                     // here lookup by attributeId is ok since we got attributeId from
1135                     // the entry via the enumeration - it's in there as is for sure
1136                     for ( Value value : attribute )
1137                     {
1138                         String normalized =  value.getNormalized();
1139                         ( ( Index ) userIndex ).drop( partitionTxn, normalized, id );
1140                     }
1141 
1142                     presenceIdx.drop( partitionTxn, attributeOid, id );
1143                 }
1144             }
1145 
1146             lockWrite();
1147 
1148             try
1149             {
1150                 rdnIdx.drop( partitionTxn, id );
1151 
1152                 updatePiarCache( parent, id, DEL_CACHE );
1153 
1154                 entryDnCache.invalidate( id );
1155                 
1156                 Attribute csn = entry.get( entryCsnAT );
1157                 // can be null while doing subentry deletion
1158                 if ( csn != null )
1159                 {
1160                     setContextCsn( csn.getString() );
1161                 }
1162 
1163                 master.remove( partitionTxn, id );
1164             }
1165             finally
1166             {
1167                 unlockWrite();
1168             }
1169 
1170             if ( isSyncOnWrite.get() )
1171             {
1172                 sync();
1173             }
1174 
1175             return entry;
1176         }
1177         catch ( Exception e )
1178         {
1179             throw new LdapOperationErrorException( e.getMessage(), e );
1180         }
1181     }
1182 
1183 
1184     //---------------------------------------------------------------------------------------------
1185     // The Search operation
1186     //---------------------------------------------------------------------------------------------
1187     /**
1188      * {@inheritDoc}
1189      */
1190     @Override
1191     public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException
1192     {
1193         PartitionTxn partitionTxn = searchContext.getTransaction();
1194         
1195         assert ( partitionTxn != null );
1196 
1197         try
1198         {
1199             setRWLock( searchContext );
1200 
1201             if ( ctxCsnChanged && getSuffixDn().equals( searchContext.getDn() ) )
1202             {
1203                 try
1204                 {
1205                     ctxCsnSemaphore.acquire();
1206                     saveContextCsn( partitionTxn );
1207                     ctxCsnChanged = false;
1208                 }
1209                 catch ( Exception e )
1210                 {
1211                     throw new LdapOperationErrorException( e.getMessage(), e );
1212                 }
1213                 finally
1214                 {
1215                     ctxCsnSemaphore.release();
1216                 }
1217             }
1218             
1219             PartitionSearchResult searchResult = searchEngine.computeResult( partitionTxn, schemaManager, searchContext );
1220 
1221             Cursor<Entry> result = new EntryCursorAdaptor( partitionTxn, this, searchResult );
1222 
1223             return new EntryFilteringCursorImpl( result, searchContext, schemaManager );
1224         }
1225         catch ( LdapException le )
1226         {
1227             // TODO: SearchEngine.cursor() should only throw LdapException, then the exception handling here can be removed
1228             throw le;
1229         }
1230         catch ( Exception e )
1231         {
1232             throw new LdapOperationErrorException( e.getMessage(), e );
1233         }
1234     }
1235 
1236 
1237     //---------------------------------------------------------------------------------------------
1238     // The Lookup operation
1239     //---------------------------------------------------------------------------------------------
1240     /**
1241      * {@inheritDoc}
1242      */
1243     @Override
1244     public Entry lookup( LookupOperationContext lookupContext ) throws LdapException
1245     {
1246         PartitionTxn partitionTxn = lookupContext.getTransaction();
1247         
1248         assert ( partitionTxn != null );
1249 
1250         try
1251         {
1252             setRWLock( lookupContext );
1253             String id = getEntryId( partitionTxn, lookupContext.getDn() );
1254     
1255             if ( id == null )
1256             {
1257                 return null;
1258             }
1259     
1260             if ( ctxCsnChanged && getSuffixDn().getNormName().equals( lookupContext.getDn().getNormName() ) )
1261             {
1262                 try
1263                 {
1264                     ctxCsnSemaphore.acquire();
1265                     saveContextCsn( partitionTxn );
1266                 }
1267                 catch ( Exception e )
1268                 {
1269                     throw new LdapOperationErrorException( e.getMessage(), e );
1270                 }
1271                 finally
1272                 {
1273                     ctxCsnSemaphore.release();
1274                 }
1275             }
1276     
1277             return fetch( partitionTxn, id, lookupContext.getDn() );
1278         }
1279         catch ( Exception e )
1280         {
1281             throw new LdapOperationErrorException( e.getMessage() );
1282         }
1283     }
1284 
1285 
1286     /**
1287      * Get back an entry knowing its UUID
1288      *
1289      * @param partitionTxn The transaction to use
1290      * @param id The Entry UUID we want to get back
1291      * @return The found Entry, or null if not found
1292      * @throws LdapException If the lookup failed for any reason (except a not found entry)
1293      */
1294     @Override
1295     public Entry fetch( PartitionTxn partitionTxn, String id ) throws LdapException
1296     {
1297         try
1298         {
1299             rwLock.readLock().lock();
1300 
1301             if ( id == null )
1302             {
1303                 id = "";
1304             }
1305             
1306             Dn dn = buildEntryDn( partitionTxn, id );
1307 
1308             return fetch( partitionTxn, id, dn );
1309         }
1310         catch ( Exception e )
1311         {
1312             throw new LdapOperationErrorException( e.getMessage(), e );
1313         }
1314         finally
1315         {
1316             rwLock.readLock().unlock();
1317         }
1318     }
1319 
1320 
1321     /**
1322      * Get back an entry knowing its UUID
1323      *
1324      * @param partitionTxn The transaction to use
1325      * @param id The Entry UUID we want to get back
1326      * @return The found Entry, or null if not found
1327      * @throws LdapException If the lookup failed for any reason (except a not found entry)
1328      */
1329     @Override
1330     public Entry fetch( PartitionTxn partitionTxn, String id, Dn dn ) throws LdapException
1331     {
1332         try
1333         {
1334             Entry entry = lookupCache( id );
1335 
1336             if ( entry != null )
1337             {
1338                 entry.setDn( dn );
1339 
1340                 entry = new ClonedServerEntry( entry );
1341 
1342                 // Replace the entry's DN with the provided one
1343                 Attribute entryDnAt = entry.get( entryDnAT );
1344                 Value dnValue = new Value( entryDnAT, dn.getName(), dn.getNormName() );
1345 
1346                 if ( entryDnAt == null )
1347                 {
1348                     entry.add( entryDnAT, dnValue );
1349                 }
1350                 else
1351                 {
1352                     entryDnAt.clear();
1353                     entryDnAt.add( dnValue );
1354                 }
1355 
1356                 return entry;
1357             }
1358 
1359             try
1360             {
1361                 rwLock.readLock().lock();
1362                 entry = master.get( partitionTxn, id );
1363             }
1364             finally
1365             {
1366                 rwLock.readLock().unlock();
1367             }
1368 
1369             if ( entry != null )
1370             {
1371                 // We have to store the DN in this entry
1372                 entry.setDn( dn );
1373 
1374                 // always store original entry in the cache
1375                 addToCache( id, entry );
1376 
1377                 entry = new ClonedServerEntry( entry );
1378 
1379                 if ( !entry.containsAttribute( entryDnAT ) )
1380                 {
1381                     entry.add( entryDnAT, dn.getName() );
1382                 }
1383 
1384                 return entry;
1385             }
1386 
1387             return null;
1388         }
1389         catch ( Exception e )
1390         {
1391             throw new LdapOperationErrorException( e.getMessage(), e );
1392         }
1393     }
1394 
1395 
1396     //---------------------------------------------------------------------------------------------
1397     // The Modify operation
1398     //---------------------------------------------------------------------------------------------
1399     /**
1400      * {@inheritDoc}
1401      */
1402     @Override
1403     public void modify( ModifyOperationContext modifyContext ) throws LdapException
1404     {
1405         PartitionTxn partitionTxn = modifyContext.getTransaction();
1406         
1407         assert ( partitionTxn != null );
1408         assert ( partitionTxn instanceof PartitionWriteTxn );
1409 
1410         try
1411         {
1412             setRWLock( modifyContext );
1413 
1414             Entry modifiedEntry = modify( partitionTxn, modifyContext.getDn(),
1415                 modifyContext.getModItems().toArray( new Modification[]
1416                     {} ) );
1417 
1418             modifyContext.setAlteredEntry( modifiedEntry );
1419 
1420             updateCache( modifyContext );
1421         }
1422         catch ( Exception e )
1423         {
1424             throw new LdapOperationErrorException( e.getMessage(), e );
1425         }
1426     }
1427 
1428 
1429     /**
1430      * {@inheritDoc}
1431      */
1432     @Override
1433     public final synchronized Entry modify( PartitionTxn partitionTxn, Dn dn, Modification... mods ) throws LdapException
1434     {
1435         String id = getEntryId( partitionTxn, dn );
1436         Entry entry = master.get( partitionTxn, id );
1437 
1438         for ( Modification mod : mods )
1439         {
1440             Attribute attrMods = mod.getAttribute();
1441 
1442             try
1443             { 
1444                 switch ( mod.getOperation() )
1445                 {
1446                     case ADD_ATTRIBUTE:
1447                         modifyAdd( partitionTxn, id, entry, attrMods );
1448                         break;
1449     
1450                     case REMOVE_ATTRIBUTE:
1451                         modifyRemove( partitionTxn, id, entry, attrMods );
1452                         break;
1453     
1454                     case REPLACE_ATTRIBUTE:
1455                         modifyReplace( partitionTxn, id, entry, attrMods );
1456                         break;
1457     
1458                     case INCREMENT_ATTRIBUTE:
1459                         modifyIncrement( partitionTxn, id, entry, attrMods );
1460                         break;
1461     
1462                     default:
1463                         throw new LdapException( I18n.err( I18n.ERR_221 ) );
1464                 }
1465             }
1466             catch ( IndexNotFoundException infe )
1467             {
1468                 throw new LdapOtherException( infe.getMessage(), infe );
1469             }
1470         }
1471 
1472         updateCsnIndex( partitionTxn, entry, id );
1473 
1474         // Remove the EntryDN
1475         entry.removeAttributes( entryDnAT );
1476 
1477         setContextCsn( entry.get( entryCsnAT ).getString() );
1478         
1479         master.put( partitionTxn, id, entry );
1480 
1481         return entry;
1482     }
1483 
1484 
1485     /**
1486      * Adds a set of attribute values while affecting the appropriate userIndices.
1487      * The entry is not persisted: it is only changed in anticipation for a put
1488      * into the master table.
1489      *
1490      * @param partitionTxn The transaction to use
1491      * @param id the primary key of the entry
1492      * @param entry the entry to alter
1493      * @param mods the attribute and values to add
1494      * @throws Exception if index alteration or attribute addition fails
1495      */
1496     @SuppressWarnings("unchecked")
1497     private void modifyAdd( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) 
1498         throws LdapException, IndexNotFoundException
1499     {
1500         if ( entry instanceof ClonedServerEntry )
1501         {
1502             throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) );
1503         }
1504 
1505         String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
1506         String normalizedModsOid = presenceNormalizer.normalize( modsOid );
1507 
1508         AttributeType attributeType = mods.getAttributeType();
1509 
1510         // Special case for the ObjectClass index
1511         if ( modsOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
1512         {
1513             for ( Value value : mods )
1514             {
1515                 if ( value.equals( topOCValue ) )
1516                 {
1517                     continue;
1518                 }
1519                 
1520                 String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1521 
1522                 objectClassIdx.add( partitionTxn, normalizedOc, id );
1523             }
1524         }
1525         else if ( hasUserIndexOn( attributeType ) )
1526         {
1527             Index<?, String> userIndex = getUserIndex( attributeType );
1528 
1529             if ( mods.size() > 0 )
1530             {
1531                 for ( Value value : mods )
1532                 {
1533                     String normalized = value.getNormalized();
1534                     ( ( Index ) userIndex ).add( partitionTxn, normalized, id );
1535                 }
1536             }
1537             else
1538             {
1539                 // Special case when we have null values
1540                 ( ( Index ) userIndex ).add( partitionTxn, null, id );
1541             }
1542 
1543             // If the attr didn't exist for this id add it to presence index
1544             if ( !presenceIdx.forward( partitionTxn, normalizedModsOid, id ) )
1545             {
1546                 presenceIdx.add( partitionTxn, normalizedModsOid, id );
1547             }
1548         }
1549         // Special case for the AdministrativeRole index
1550         else if ( modsOid.equals( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ) )
1551         {
1552             // We may have more than one role 
1553             for ( Value value : mods )
1554             {
1555                 adminRoleIdx.add( partitionTxn, value.getString(), id );
1556             }
1557 
1558             // If the attr didn't exist for this id add it to presence index
1559             if ( !presenceIdx.forward( partitionTxn, normalizedModsOid, id ) )
1560             {
1561                 presenceIdx.add( partitionTxn, normalizedModsOid, id );
1562             }
1563         }
1564 
1565         // add all the values in mods to the same attribute in the entry
1566         if ( mods.size() > 0 )
1567         {
1568             for ( Value value : mods )
1569             {
1570                 entry.add( mods.getAttributeType(), value );
1571             }
1572         }
1573         else
1574         {
1575             // Special cases for null values
1576             if ( mods.getAttributeType().getSyntax().isHumanReadable() )
1577             {
1578                 entry.add( mods.getAttributeType(), new Value( mods.getAttributeType(), ( String ) null ) );
1579             }
1580             else
1581             {
1582                 entry.add( mods.getAttributeType(), new Value( mods.getAttributeType(), ( byte[] ) null ) );
1583             }
1584         }
1585 
1586         if ( modsOid.equals( SchemaConstants.ALIASED_OBJECT_NAME_AT_OID ) )
1587         {
1588             Dn ndn = getEntryDn( partitionTxn, id );
1589             addAliasIndices( partitionTxn, id, ndn, new Dn( schemaManager, mods.getString() ) );
1590         }
1591     }
1592 
1593 
1594     /**
1595      * Completely replaces the existing set of values for an attribute with the
1596      * modified values supplied affecting the appropriate userIndices.  The entry
1597      * is not persisted: it is only changed in anticipation for a put into the
1598      * master table.
1599      *
1600      * @param partitionTxn The transaction to use
1601      * @param id the primary key of the entry
1602      * @param entry the entry to alter
1603      * @param mods the replacement attribute and values
1604      * @throws Exception if index alteration or attribute modification
1605      * fails.
1606      */
1607     @SuppressWarnings("unchecked")
1608     private void modifyReplace( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) 
1609         throws LdapException, IndexNotFoundException
1610     {
1611         if ( entry instanceof ClonedServerEntry )
1612         {
1613             throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) );
1614         }
1615 
1616         String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
1617         AttributeType attributeType = mods.getAttributeType();
1618 
1619         // Special case for the ObjectClass index
1620         if ( attributeType.equals( objectClassAT ) )
1621         {
1622             // if the id exists in the index drop all existing attribute
1623             // value index entries and add new ones
1624             for ( Value value : entry.get( objectClassAT ) )
1625             {
1626                 if ( value.equals( topOCValue ) )
1627                 {
1628                     continue;
1629                 }
1630                 
1631                 String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1632 
1633                 objectClassIdx.drop( partitionTxn, normalizedOc, id );
1634             }
1635 
1636             for ( Value value : mods )
1637             {
1638                 if ( value.equals( topOCValue ) )
1639                 {
1640                     continue;
1641                 }
1642                 
1643                 String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1644 
1645                 objectClassIdx.add( partitionTxn, normalizedOc, id );
1646             }
1647         }
1648         else if ( hasUserIndexOn( attributeType ) )
1649         {
1650             Index<?, String> userIndex = getUserIndex( attributeType );
1651 
1652             // Drop all the previous values
1653             Attribute oldAttribute = entry.get( mods.getAttributeType() );
1654 
1655             if ( oldAttribute != null )
1656             {
1657                 for ( Value value : oldAttribute )
1658                 {
1659                     String normalized = value.getNormalized();
1660                     ( ( Index<Object, String> ) userIndex ).drop( partitionTxn, normalized, id );
1661                 }
1662             }
1663 
1664             // And add the new ones
1665             for ( Value value : mods )
1666             {
1667                 String normalized = value.getNormalized();
1668                 ( ( Index ) userIndex ).add( partitionTxn, normalized, id );
1669             }
1670 
1671             /*
1672              * If we have no new value, we have to drop the AT fro the presence index
1673              */
1674             if ( mods.size() == 0 )
1675             {
1676                 presenceIdx.drop( partitionTxn, modsOid, id );
1677             }
1678         }
1679         // Special case for the AdministrativeRole index
1680         else if ( attributeType.equals( administrativeRoleAT ) )
1681         {
1682             // Remove the previous values
1683             for ( Value value : entry.get( administrativeRoleAT ) )
1684             {
1685                 if ( value.equals( topOCValue ) )
1686                 {
1687                     continue;
1688                 }
1689                 
1690                 String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1691 
1692                 objectClassIdx.drop( partitionTxn, normalizedOc, id );
1693             }
1694 
1695             // And add the new ones 
1696             for ( Value value : mods )
1697             {
1698                 String valueStr = value.getString();
1699 
1700                 if ( valueStr.equals( topOCValue ) )
1701                 {
1702                     continue;
1703                 }
1704                 
1705                 adminRoleIdx.add( partitionTxn, valueStr, id );
1706             }
1707         }
1708 
1709         String aliasAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName(
1710             SchemaConstants.ALIASED_OBJECT_NAME_AT );
1711 
1712         if ( mods.getAttributeType().equals( aliasedObjectNameAT ) )
1713         {
1714             dropAliasIndices( partitionTxn, id );
1715         }
1716 
1717         // replaces old attributes with new modified ones if they exist
1718         if ( mods.size() > 0 )
1719         {
1720             entry.put( mods );
1721         }
1722         else
1723         // removes old attributes if new replacements do not exist
1724         {
1725             entry.remove( mods );
1726         }
1727 
1728         if ( modsOid.equals( aliasAttributeOid ) && mods.size() > 0 )
1729         {
1730             Dn entryDn = getEntryDn( partitionTxn, id );
1731             addAliasIndices( partitionTxn, id, entryDn, new Dn( schemaManager, mods.getString() ) );
1732         }
1733     }
1734 
1735 
1736     /**
1737      * Completely replaces the existing set of values for an attribute with the
1738      * modified values supplied affecting the appropriate userIndices.  The entry
1739      * is not persisted: it is only changed in anticipation for a put into the
1740      * master table.
1741      *
1742      * @param partitionTxn The transaction to use
1743      * @param id the primary key of the entry
1744      * @param entry the entry to alter
1745      * @param mods the replacement attribute and values
1746      * @throws Exception if index alteration or attribute modification
1747      * fails.
1748      */
1749     @SuppressWarnings("unchecked")
1750     private void modifyIncrement( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) 
1751         throws LdapException, IndexNotFoundException
1752     {
1753         if ( entry instanceof ClonedServerEntry )
1754         {
1755             throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) );
1756         }
1757 
1758         String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
1759         AttributeType attributeType = mods.getAttributeType();
1760 
1761         // Special case for the ObjectClass index
1762         if ( attributeType.equals( objectClassAT ) )
1763         {
1764             // if the id exists in the index drop all existing attribute
1765             // value index entries and add new ones
1766             for ( Value value : entry.get( objectClassAT ) )
1767             {
1768                 if ( value.equals( topOCValue ) )
1769                 {
1770                     continue;
1771                 }
1772                 
1773                 String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1774 
1775                 objectClassIdx.drop( partitionTxn, normalizedOc, id );
1776             }
1777 
1778             for ( Value value : mods )
1779             {
1780                 if ( value.equals( topOCValue ) )
1781                 {
1782                     continue;
1783                 }
1784                 
1785                 String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1786 
1787                 objectClassIdx.add( partitionTxn, normalizedOc, id );
1788             }
1789         }
1790         else if ( hasUserIndexOn( attributeType ) )
1791         {
1792             Index<?, String> userIndex = getUserIndex( attributeType );
1793 
1794             // Drop all the previous values
1795             Attribute oldAttribute = entry.get( mods.getAttributeType() );
1796 
1797             if ( oldAttribute != null )
1798             {
1799                 for ( Value value : oldAttribute )
1800                 {
1801                     String normalized = value.getNormalized();
1802                     ( ( Index<Object, String> ) userIndex ).drop( partitionTxn, normalized, id );
1803                 }
1804             }
1805 
1806             // And add the new ones
1807             for ( Value value : mods )
1808             {
1809                 String normalized = value.getNormalized();
1810                 ( ( Index ) userIndex ).add( partitionTxn, normalized, id );
1811             }
1812 
1813             /*
1814              * If we have no new value, we have to drop the AT fro the presence index
1815              */
1816             if ( mods.size() == 0 )
1817             {
1818                 presenceIdx.drop( partitionTxn, modsOid, id );
1819             }
1820         }
1821         // Special case for the AdministrativeRole index
1822         else if ( attributeType.equals( administrativeRoleAT ) )
1823         {
1824             // Remove the previous values
1825             for ( Value value : entry.get( administrativeRoleAT ) )
1826             {
1827                 if ( value.equals( topOCValue ) )
1828                 {
1829                     continue;
1830                 }
1831                 
1832                 String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1833 
1834                 objectClassIdx.drop( partitionTxn, normalizedOc, id );
1835             }
1836 
1837             // And add the new ones 
1838             for ( Value value : mods )
1839             {
1840                 String valueStr = value.getString();
1841 
1842                 if ( valueStr.equals( topOCValue ) )
1843                 {
1844                     continue;
1845                 }
1846                 
1847                 adminRoleIdx.add( partitionTxn, valueStr, id );
1848             }
1849         }
1850 
1851         String aliasAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName(
1852             SchemaConstants.ALIASED_OBJECT_NAME_AT );
1853 
1854         if ( mods.getAttributeType().equals( aliasedObjectNameAT ) )
1855         {
1856             dropAliasIndices( partitionTxn, id );
1857         }
1858 
1859         // replaces old attributes with new modified ones if they exist
1860         Attribute attribute = entry.get( mods.getAttributeType() );
1861         Value[] newValues = new Value[ attribute.size() ];
1862         int increment = 1;
1863         int i = 0;
1864         
1865         if ( mods.size() != 0 )
1866         {
1867             increment = Integer.parseInt( mods.getString() );
1868         }
1869 
1870         for ( Value value : attribute )
1871         {
1872             int intValue = Integer.parseInt( value.getNormalized() );
1873             
1874             if ( intValue >= Integer.MAX_VALUE - increment )
1875             {
1876                 throw new IllegalArgumentException( "Increment operation overflow for attribute" 
1877                     + attributeType );
1878             }
1879             
1880             newValues[i++] = new Value( Integer.toString( intValue + increment ) );
1881             attribute.remove( value );
1882         }
1883         
1884         attribute.add( newValues );
1885 
1886         if ( modsOid.equals( aliasAttributeOid ) && mods.size() > 0 )
1887         {
1888             Dn entryDn = getEntryDn( partitionTxn, id );
1889             addAliasIndices( partitionTxn, id, entryDn, new Dn( schemaManager, mods.getString() ) );
1890         }
1891     }
1892 
1893 
1894     /**
1895      * Completely removes the set of values for an attribute having the values
1896      * supplied while affecting the appropriate userIndices.  The entry is not
1897      * persisted: it is only changed in anticipation for a put into the master
1898      * table.  Note that an empty attribute w/o values will remove all the
1899      * values within the entry where as an attribute w/ values will remove those
1900      * attribute values it contains.
1901      *
1902      * @param partitionTxn The transaction to use
1903      * @param id the primary key of the entry
1904      * @param entry the entry to alter
1905      * @param mods the attribute and its values to delete
1906      * @throws Exception if index alteration or attribute modification fails.
1907      */
1908     @SuppressWarnings("unchecked")
1909     private void modifyRemove( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) 
1910         throws LdapException, IndexNotFoundException
1911     {
1912         if ( entry instanceof ClonedServerEntry )
1913         {
1914             throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) );
1915         }
1916 
1917         String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
1918         AttributeType attributeType = mods.getAttributeType();
1919 
1920         // Special case for the ObjectClass index
1921         if ( attributeType.equals( objectClassAT ) )
1922         {
1923             /*
1924              * If there are no attribute values in the modifications then this
1925              * implies the complete removal of the attribute from the index. Else
1926              * we remove individual tuples from the index.
1927              */
1928             if ( mods.size() == 0 )
1929             {
1930                 for ( Value value : entry.get( objectClassAT ) )
1931                 {
1932                     if ( value.equals( topOCValue ) )
1933                     {
1934                         continue;
1935                     }
1936                     
1937                     String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1938 
1939                     objectClassIdx.drop( partitionTxn, normalizedOc, id );
1940                 }
1941             }
1942             else
1943             {
1944                 for ( Value value : mods )
1945                 {
1946                     if ( value.equals( topOCValue ) )
1947                     {
1948                         continue;
1949                     }
1950                     
1951                     String normalizedOc = objectClassNormalizer.normalize( value.getString() );
1952 
1953                     objectClassIdx.drop( partitionTxn, normalizedOc, id );
1954                 }
1955             }
1956         }
1957         else if ( hasUserIndexOn( attributeType ) )
1958         {
1959             Index<?, String> userIndex = getUserIndex( attributeType );
1960 
1961             Attribute attribute = entry.get( attributeType ).clone();
1962             int nbValues = 0;
1963 
1964             if ( attribute != null )
1965             {
1966                 nbValues = attribute.size();
1967             }
1968 
1969             /*
1970              * If there are no attribute values in the modifications then this
1971              * implies the complete removal of the attribute from the index. Else
1972              * we remove individual tuples from the index.
1973              */
1974             if ( mods.size() == 0 )
1975             {
1976                 ( ( Index ) userIndex ).drop( partitionTxn, id );
1977                 nbValues = 0;
1978             }
1979             else if ( nbValues > 0 )
1980             {
1981                 for ( Value value : mods )
1982                 {
1983                     if ( attribute.contains( value ) )
1984                     {
1985                         nbValues--;
1986                         attribute.remove( value );
1987                     }
1988 
1989                     String normalized = value.getNormalized();
1990                     ( ( Index ) userIndex ).drop( partitionTxn, normalized, id );
1991                 }
1992             }
1993 
1994             /*
1995              * If no attribute values exist for this entryId in the index then
1996              * we remove the presence index entry for the removed attribute.
1997              */
1998             if ( nbValues == 0 )
1999             {
2000                 presenceIdx.drop( partitionTxn, modsOid, id );
2001             }
2002         }
2003         // Special case for the AdministrativeRole index
2004         else if ( modsOid.equals( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ) )
2005         {
2006             // We may have more than one role 
2007             for ( Value value : mods )
2008             {
2009                 adminRoleIdx.drop( partitionTxn, value.getString(), id );
2010             }
2011 
2012             /*
2013              * If no attribute values exist for this entryId in the index then
2014              * we remove the presence index entry for the removed attribute.
2015              */
2016             if ( null == adminRoleIdx.reverseLookup( partitionTxn, id ) )
2017             {
2018                 presenceIdx.drop( partitionTxn, modsOid, id );
2019             }
2020         }
2021 
2022         /*
2023          * If there are no attribute values in the modifications then this
2024          * implies the complete removal of the attribute from the entry. Else
2025          * we remove individual attribute values from the entry in mods one
2026          * at a time.
2027          */
2028         if ( mods.size() == 0 )
2029         {
2030             entry.removeAttributes( mods.getAttributeType() );
2031         }
2032         else
2033         {
2034             Attribute entryAttr = entry.get( mods.getAttributeType() );
2035 
2036             // Allow for null to fix DIRSERVER-2135
2037             if ( entryAttr != null )
2038             {
2039                 for ( Value value : mods )
2040                 {
2041                     entryAttr.remove( value );
2042                 }
2043     
2044                 // if nothing is left just remove empty attribute
2045                 if ( entryAttr.size() == 0 )
2046                 {
2047                     entry.removeAttributes( entryAttr.getId() );
2048                 }
2049             }
2050         }
2051 
2052         // Aliases->single valued comp/partial attr removal is not relevant here
2053         if ( mods.getAttributeType().equals( aliasedObjectNameAT ) )
2054         {
2055             dropAliasIndices( partitionTxn, id );
2056         }
2057     }
2058 
2059 
2060     //---------------------------------------------------------------------------------------------
2061     // The Move operation
2062     //---------------------------------------------------------------------------------------------
2063     /**
2064      * {@inheritDoc}
2065      */
2066     @Override
2067     public void move( MoveOperationContext moveContext ) throws LdapException
2068     {
2069         if ( moveContext.getNewSuperior().isDescendantOf( moveContext.getDn() ) )
2070         {
2071             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
2072                 "cannot place an entry below itself" );
2073         }
2074         
2075         PartitionTxn partitionTxn = moveContext.getTransaction();
2076 
2077         assert ( partitionTxn != null );
2078         assert ( partitionTxn instanceof PartitionWriteTxn );
2079 
2080         try
2081         {
2082             setRWLock( moveContext );
2083             Dn oldDn = moveContext.getDn();
2084             Dn newSuperior = moveContext.getNewSuperior();
2085             Dn newDn = moveContext.getNewDn();
2086             Entry modifiedEntry = moveContext.getModifiedEntry();
2087 
2088             move( partitionTxn, oldDn, newSuperior, newDn, modifiedEntry );
2089             updateCache( moveContext );
2090         }
2091         catch ( Exception e )
2092         {
2093             throw new LdapOperationErrorException( e.getMessage(), e );
2094         }
2095     }
2096 
2097 
2098     /**
2099      * {@inheritDoc}
2100      */
2101     @Override
2102     public final synchronized void move( PartitionTxn partitionTxn, Dn oldDn, Dn newSuperiorDn, Dn newDn, Entry modifiedEntry )
2103         throws LdapException
2104     {
2105         // Check that the parent Dn exists
2106         String newParentId = getEntryId( partitionTxn, newSuperiorDn );
2107 
2108         if ( newParentId == null )
2109         {
2110             // This is not allowed : the parent must exist
2111             throw new LdapEntryAlreadyExistsException(
2112                 I18n.err( I18n.ERR_256_NO_SUCH_OBJECT, newSuperiorDn.getName() ) );
2113         }
2114 
2115         // Now check that the new entry does not exist
2116         String newId = getEntryId( partitionTxn, newDn );
2117 
2118         if ( newId != null )
2119         {
2120             // This is not allowed : we should not be able to move an entry
2121             // to an existing position
2122             throw new LdapEntryAlreadyExistsException(
2123                 I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, newSuperiorDn.getName() ) );
2124         }
2125 
2126         // Get the entry and the old parent IDs
2127         String entryId = getEntryId( partitionTxn, oldDn );
2128         String oldParentId = getParentId( partitionTxn, entryId );
2129 
2130         /*
2131          * All aliases including and below oldChildDn, will be affected by
2132          * the move operation with respect to one and subtree userIndices since
2133          * their relationship to ancestors above oldChildDn will be
2134          * destroyed.  For each alias below and including oldChildDn we will
2135          * drop the index tuples mapping ancestor ids above oldChildDn to the
2136          * respective target ids of the aliases.
2137          */
2138         dropMovedAliasIndices( partitionTxn, oldDn );
2139 
2140         // Update the Rdn index
2141         // First drop the old entry
2142         ParentIdAndRdn movedEntry = rdnIdx.reverseLookup( partitionTxn, entryId );
2143 
2144         updateRdnIdx( partitionTxn, oldParentId, REMOVE_CHILD, movedEntry.getNbDescendants() );
2145 
2146         rdnIdx.drop( partitionTxn, entryId );
2147         updatePiarCache( movedEntry, entryId, DEL_CACHE );
2148 
2149         // Now, add the new entry at the right position
2150         movedEntry.setParentId( newParentId );
2151         rdnIdx.add( partitionTxn, movedEntry, entryId );
2152         updatePiarCache( movedEntry, entryId, ADD_CACHE );
2153 
2154         updateRdnIdx( partitionTxn, newParentId, ADD_CHILD, movedEntry.getNbDescendants() );
2155 
2156         /*
2157          * Read Alias Index Tuples
2158          *
2159          * If this is a name change due to a move operation then the one and
2160          * subtree userIndices for aliases were purged before the aliases were
2161          * moved.  Now we must add them for each alias entry we have moved.
2162          *
2163          * aliasTarget is used as a marker to tell us if we're moving an
2164          * alias.  If it is null then the moved entry is not an alias.
2165          */
2166         Dn aliasTarget = aliasIdx.reverseLookup( partitionTxn, entryId );
2167 
2168         if ( null != aliasTarget )
2169         {
2170             if ( !aliasTarget.isSchemaAware() )
2171             {
2172                 aliasTarget = new Dn( schemaManager, aliasTarget );
2173             }
2174             
2175 
2176             addAliasIndices( partitionTxn, entryId, buildEntryDn( partitionTxn, entryId ), aliasTarget );
2177         }
2178 
2179         // the below case arises only when the move( Dn oldDn, Dn newSuperiorDn, Dn newDn  ) is called
2180         // directly using the Store API, in this case the value of modified entry will be null
2181         // we need to lookup the entry to update the parent UUID
2182         if ( modifiedEntry == null )
2183         {
2184             modifiedEntry = fetch( partitionTxn, entryId );
2185         }
2186 
2187         // Update the master table with the modified entry
2188         modifiedEntry.put( ApacheSchemaConstants.ENTRY_PARENT_ID_AT, newParentId );
2189 
2190         // Remove the EntryDN
2191         modifiedEntry.removeAttributes( entryDnAT );
2192 
2193         entryDnCache.invalidateAll();
2194         
2195         setContextCsn( modifiedEntry.get( entryCsnAT ).getString() );
2196 
2197         master.put( partitionTxn, entryId, modifiedEntry );
2198 
2199         if ( isSyncOnWrite.get() )
2200         {
2201             sync();
2202         }
2203     }
2204 
2205 
2206     //---------------------------------------------------------------------------------------------
2207     // The MoveAndRename operation
2208     //---------------------------------------------------------------------------------------------
2209     /**
2210      * {@inheritDoc}
2211      */
2212     @Override
2213     public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
2214     {
2215         if ( moveAndRenameContext.getNewSuperiorDn().isDescendantOf( moveAndRenameContext.getDn() ) )
2216         {
2217             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
2218                 "cannot place an entry below itself" );
2219         }
2220 
2221         PartitionTxn partitionTxn = moveAndRenameContext.getTransaction();
2222 
2223         assert ( partitionTxn != null );
2224         assert ( partitionTxn instanceof PartitionWriteTxn );
2225 
2226         try
2227         {
2228             setRWLock( moveAndRenameContext );
2229             Dn oldDn = moveAndRenameContext.getDn();
2230             Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn();
2231             Rdn newRdn = moveAndRenameContext.getNewRdn();
2232             Entry modifiedEntry = moveAndRenameContext.getModifiedEntry();
2233             Map<String, List<ModDnAva>> modAvas = moveAndRenameContext.getModifiedAvas();
2234 
2235             moveAndRename( partitionTxn, oldDn, newSuperiorDn, newRdn, modAvas, modifiedEntry );
2236             updateCache( moveAndRenameContext );
2237         }
2238         catch ( LdapException le )
2239         {
2240             // In case we get an LdapException, just rethrow it as is to
2241             // avoid having it lost
2242             throw le;
2243         }
2244         catch ( Exception e )
2245         {
2246             throw new LdapOperationErrorException( e.getMessage(), e );
2247         }
2248     }
2249 
2250 
2251     /**
2252      * Moves an entry under a new parent.  The operation causes a shift in the
2253      * parent child relationships between the old parent, new parent and the
2254      * child moved.  All other descendant entries under the child never change
2255      * their direct parent child relationships.  Hence after the parent child
2256      * relationship changes are broken at the old parent and set at the new
2257      * parent a modifyDn operation is conducted to handle name changes
2258      * propagating down through the moved child and its descendants.
2259      *
2260      * @param oldDn the normalized dn of the child to be moved
2261      * @param newSuperiorDn the id of the child being moved
2262      * @param newRdn the normalized dn of the new parent for the child
2263      * @param modAvas The modified Avas
2264      * @param modifiedEntry the modified entry
2265      * @throws LdapException if something goes wrong
2266      */
2267     @Override
2268     public void moveAndRename( PartitionTxn partitionTxn, Dn oldDn, Dn newSuperiorDn, Rdn newRdn, Map<String, 
2269             List<ModDnAva>> modAvas, Entry modifiedEntry ) throws LdapException
2270     {
2271         // Get the child and the new parent to be entries and Ids
2272         Attribute entryIdAt = modifiedEntry.get( SchemaConstants.ENTRY_UUID_AT );
2273         String entryId;
2274         
2275         if ( entryIdAt == null )
2276         {
2277             entryId = getEntryId( partitionTxn, modifiedEntry.getDn() );
2278         }
2279         else
2280         {
2281             entryId = modifiedEntry.get( SchemaConstants.ENTRY_UUID_AT ).getString();
2282         }
2283 
2284         Attribute oldParentIdAt = modifiedEntry.get( ApacheSchemaConstants.ENTRY_PARENT_ID_AT );
2285         String oldParentId;
2286         
2287         if ( oldParentIdAt == null )
2288         {
2289             oldParentId = getEntryId( partitionTxn, oldDn.getParent() );
2290         }
2291         else
2292         {
2293             oldParentId = oldParentIdAt.getString();
2294         }
2295 
2296         String newParentId = getEntryId( partitionTxn, newSuperiorDn );
2297 
2298         //Get the info about the moved entry
2299         ParentIdAndRdn movedEntry = rdnIdx.reverseLookup( partitionTxn, entryId );
2300         
2301         // First drop the moved entry from the rdn index
2302         rdnIdx.drop( partitionTxn, entryId );
2303         updatePiarCache( movedEntry, entryId, DEL_CACHE );
2304 
2305         //
2306         // The update the Rdn index. We will remove the ParentIdAndRdn associated with the
2307         // moved entry, and update the nbChilden of its parent and the nbSubordinates
2308         // of all its ascendant, up to the common superior.
2309         // Then we will add a ParentidAndRdn for the moved entry under the new superior,
2310         // update its children number and the nbSubordinates of all the new ascendant.
2311         updateRdnIdx( partitionTxn, oldParentId, REMOVE_CHILD, movedEntry.getNbDescendants() );
2312 
2313         /*
2314          * All aliases including and below oldChildDn, will be affected by
2315          * the move operation with respect to one and subtree userIndices since
2316          * their relationship to ancestors above oldChildDn will be
2317          * destroyed.  For each alias below and including oldChildDn we will
2318          * drop the index tuples mapping ancestor ids above oldChildDn to the
2319          * respective target ids of the aliases.
2320          */
2321         dropMovedAliasIndices( partitionTxn, oldDn );
2322 
2323         // Now, add the new entry at the right position
2324         // First
2325         movedEntry.setParentId( newParentId );
2326         movedEntry.setRdns( new Rdn[]
2327             { newRdn } );
2328         rdnIdx.add( partitionTxn, movedEntry, entryId );
2329         updatePiarCache( movedEntry, entryId, ADD_CACHE );
2330 
2331         updateRdnIdx( partitionTxn, newParentId, ADD_CHILD, movedEntry.getNbDescendants() );
2332 
2333         // Process the modified indexes now
2334         try
2335         {
2336             processModifiedAvas( partitionTxn, modAvas, entryId );
2337         }
2338         catch ( IndexNotFoundException infe )
2339         {
2340             throw new LdapOtherException( infe.getMessage(), infe );
2341         }
2342 
2343         /*
2344          * Read Alias Index Tuples
2345          *
2346          * If this is a name change due to a move operation then the one and
2347          * subtree userIndices for aliases were purged before the aliases were
2348          * moved.  Now we must add them for each alias entry we have moved.
2349          *
2350          * aliasTarget is used as a marker to tell us if we're moving an
2351          * alias.  If it is null then the moved entry is not an alias.
2352          */
2353         Dn aliasTarget = aliasIdx.reverseLookup( partitionTxn, entryId );
2354 
2355         if ( null != aliasTarget )
2356         {
2357             if ( !aliasTarget.isSchemaAware() )
2358             {
2359                 aliasTarget = new Dn( schemaManager, aliasTarget );
2360             }
2361             
2362             addAliasIndices( partitionTxn, entryId, buildEntryDn( partitionTxn, entryId ), aliasTarget );
2363         }
2364 
2365         // Remove the EntryDN
2366         modifiedEntry.removeAttributes( entryDnAT );
2367         
2368         // Update the entryParentId attribute
2369         modifiedEntry.removeAttributes( ApacheSchemaConstants.ENTRY_PARENT_ID_OID );
2370         modifiedEntry.add( ApacheSchemaConstants.ENTRY_PARENT_ID_OID, newParentId );
2371         
2372         // Doom the DN cache now
2373         entryDnCache.invalidateAll();
2374 
2375         setContextCsn( modifiedEntry.get( entryCsnAT ).getString() );
2376 
2377         // save the modified entry at the new place
2378         master.put( partitionTxn, entryId, modifiedEntry );
2379     }
2380     
2381     
2382     /**
2383      * Update the index accordingly to the changed Attribute in the old and new RDN
2384      * 
2385      * @param partitionTxn The transaction to use
2386      * @param modAvs The modified AVAs
2387      * @param entryId The Entry ID
2388      * @throws {@link LdapException} If the AVA cannt be processed properly
2389      * @throws IndexNotFoundException If teh index is not found
2390      */
2391     private void processModifiedAvas( PartitionTxn partitionTxn, Map<String, List<ModDnAva>> modAvas, String entryId ) 
2392         throws LdapException, IndexNotFoundException
2393     {
2394         for ( List<ModDnAva> modDnAvas : modAvas.values() )
2395         {
2396             for ( ModDnAva modDnAva : modDnAvas )
2397             {
2398                 AttributeType attributeType = modDnAva.getAva().getAttributeType();
2399                 
2400                 if ( !hasIndexOn( attributeType ) )
2401                 {
2402                     break;
2403                 }
2404 
2405                 Index<?, String> index = getUserIndex( attributeType );
2406                 
2407                 switch ( modDnAva.getType() )
2408                 {
2409                     case ADD :
2410                     case UPDATE_ADD :
2411                         // Add Value in the index
2412                         ( ( Index ) index ).add( partitionTxn, modDnAva.getAva().getValue().getNormalized(), entryId );
2413 
2414                         /*
2415                          * If there is no value for id in this index due to our
2416                          * add above we add the entry in the presence idx
2417                          */
2418                         if ( null == index.reverseLookup( partitionTxn, entryId ) )
2419                         {
2420                             presenceIdx.add( partitionTxn, attributeType.getOid(), entryId );
2421                         }
2422                         
2423                         break;
2424 
2425                     case DELETE :
2426                     case UPDATE_DELETE :
2427                         ( ( Index ) index ).drop( partitionTxn, modDnAva.getAva().getValue().getNormalized(), entryId );
2428 
2429                         /*
2430                          * If there is no value for id in this index due to our
2431                          * drop above we remove the oldRdnAttr from the presence idx
2432                          */
2433                         if ( null == index.reverseLookup( partitionTxn, entryId ) )
2434                         {
2435                             presenceIdx.drop( partitionTxn, attributeType.getOid(), entryId );
2436                         }
2437                         
2438                         break;
2439                         
2440                     default :
2441                         break;
2442                 }
2443             }
2444         }
2445     }
2446     
2447     
2448     //---------------------------------------------------------------------------------------------
2449     // The Rename operation
2450     //---------------------------------------------------------------------------------------------
2451     /**
2452      * {@inheritDoc}
2453      */
2454     @Override
2455     public void rename( RenameOperationContext renameContext ) throws LdapException
2456     {
2457         PartitionTxn partitionTxn = renameContext.getTransaction();
2458 
2459         assert ( partitionTxn != null );
2460         assert ( partitionTxn instanceof PartitionWriteTxn );
2461 
2462         try
2463         {
2464             setRWLock( renameContext );
2465             Dn oldDn = renameContext.getDn();
2466             Rdn newRdn = renameContext.getNewRdn();
2467             boolean deleteOldRdn = renameContext.getDeleteOldRdn();
2468 
2469             if ( renameContext.getEntry() != null )
2470             {
2471                 Entry modifiedEntry = renameContext.getModifiedEntry();
2472                 rename( partitionTxn, oldDn, newRdn, deleteOldRdn, modifiedEntry );
2473             }
2474             else
2475             {
2476                 rename( partitionTxn, oldDn, newRdn, deleteOldRdn, null );
2477             }
2478 
2479             updateCache( renameContext );
2480         }
2481         catch ( Exception e )
2482         {
2483             throw new LdapOperationErrorException( e.getMessage(), e );
2484         }
2485     }
2486 
2487 
2488     /**
2489      * This will rename the entry, and deal with the deleteOldRdn flag. If set to true, we have
2490      * to remove the AVA which are not part of the new RDN from the entry.
2491      * If this flag is set to false, we have to take care of the special case of an AVA
2492      * which attributeType is SINGLE-VALUE : in this case, we remove the old value.
2493      */
2494     private void rename( PartitionTxn partitionTxn, String oldId, Rdn newRdn, boolean deleteOldRdn, Entry entry ) 
2495         throws LdapException, IndexNotFoundException
2496     {
2497         if ( entry == null )
2498         {
2499             entry = master.get( partitionTxn, oldId );
2500         }
2501 
2502         Dn updn = entry.getDn();
2503 
2504         if ( !newRdn.isSchemaAware() )
2505         {
2506             newRdn = new Rdn( schemaManager, newRdn );
2507         }
2508 
2509         /*
2510          * H A N D L E   N E W   R D N
2511          * ====================================================================
2512          * Add the new Rdn attribute to the entry.  If an index exists on the
2513          * new Rdn attribute we add the index for this attribute value pair.
2514          * Also we make sure that the presence index shows the existence of the
2515          * new Rdn attribute within this entry.
2516          * Last, not least, if the AttributeType is single value, take care
2517          * of removing the old value.
2518          */
2519         for ( Ava newAtav : newRdn )
2520         {
2521             String newNormType = newAtav.getNormType();
2522             Object newNormValue = newAtav.getValue().getString();
2523 
2524             AttributeType newRdnAttrType = schemaManager.lookupAttributeTypeRegistry( newNormType );
2525 
2526             if ( newRdnAttrType.isSingleValued() && entry.containsAttribute( newRdnAttrType ) )
2527             {
2528                 Attribute oldAttribute = entry.get( newRdnAttrType );
2529                 AttributeType oldAttributeType = oldAttribute.getAttributeType();
2530                 
2531                 // We have to remove the old attribute value, if we have some
2532                 entry.removeAttributes( newRdnAttrType );
2533                 
2534                 // Deal with the index
2535                 if ( hasUserIndexOn( newRdnAttrType ) )
2536                 {
2537                     Index<?, String> userIndex = getUserIndex( newRdnAttrType );
2538 
2539                     String normalized = oldAttributeType.getEquality().getNormalizer().normalize( oldAttribute.get().getString() );
2540                     ( ( Index ) userIndex ).drop( partitionTxn, normalized, id );
2541 
2542                     /*
2543                      * If there is no value for id in this index due to our
2544                      * drop above we remove the oldRdnAttr from the presence idx
2545                      */
2546                     if ( null == userIndex.reverseLookup( partitionTxn, oldId ) )
2547                     {
2548                         presenceIdx.drop( partitionTxn, newRdnAttrType.getOid(), oldId );
2549                     }
2550                 }
2551             }
2552 
2553             if ( newRdnAttrType.getSyntax().isHumanReadable() )
2554             {
2555                 entry.add( newRdnAttrType, newAtav.getValue().getString() );
2556             }
2557             else
2558             {
2559                 entry.add( newRdnAttrType, newAtav.getValue().getBytes() );
2560             }
2561 
2562             if ( hasUserIndexOn( newRdnAttrType ) )
2563             {
2564                 Index<?, String> userIndex = getUserIndex( newRdnAttrType );
2565                 
2566                 String normalized = newRdnAttrType.getEquality().getNormalizer().normalize( ( String ) newNormValue );
2567                 ( ( Index ) userIndex ).add( partitionTxn, normalized, oldId );
2568 
2569                 // Make sure the altered entry shows the existence of the new attrib
2570                 String normTypeOid = presenceNormalizer.normalize( newNormType );
2571                 
2572                 if ( !presenceIdx.forward( partitionTxn, normTypeOid, oldId ) )
2573                 {
2574                     presenceIdx.add( partitionTxn, normTypeOid, oldId );
2575                 }
2576             }
2577         }
2578 
2579         /*
2580          * H A N D L E   O L D   R D N
2581          * ====================================================================
2582          * If the old Rdn is to be removed we need to get the attribute and
2583          * value for it.  Keep in mind the old Rdn need not be based on the
2584          * same attr as the new one.  We remove the Rdn value from the entry
2585          * and remove the value/id tuple from the index on the old Rdn attr
2586          * if any.  We also test if the delete of the old Rdn index tuple
2587          * removed all the attribute values of the old Rdn using a reverse
2588          * lookup.  If so that means we blew away the last value of the old
2589          * Rdn attribute.  In this case we need to remove the attrName/id
2590          * tuple from the presence index.
2591          *
2592          * We only remove an ATAV of the old Rdn if it is not included in the
2593          * new Rdn.
2594          */
2595 
2596         if ( deleteOldRdn )
2597         {
2598             Rdn oldRdn = updn.getRdn();
2599 
2600             for ( Ava oldAtav : oldRdn )
2601             {
2602                 // check if the new ATAV is part of the old Rdn
2603                 // if that is the case we do not remove the ATAV
2604                 boolean mustRemove = true;
2605 
2606                 for ( Ava newAtav : newRdn )
2607                 {
2608                     if ( oldAtav.equals( newAtav ) )
2609                     {
2610                         mustRemove = false;
2611                         break;
2612                     }
2613                 }
2614 
2615                 if ( mustRemove )
2616                 {
2617                     String oldNormType = oldAtav.getNormType();
2618                     String oldNormValue = oldAtav.getValue().getString();
2619                     AttributeType oldRdnAttrType = schemaManager.lookupAttributeTypeRegistry( oldNormType );
2620                     entry.remove( oldRdnAttrType, oldNormValue );
2621 
2622                     if ( hasUserIndexOn( oldRdnAttrType ) )
2623                     {
2624                         Index<?, String> userIndex = getUserIndex( oldRdnAttrType );
2625                         
2626                         String normalized = oldRdnAttrType.getEquality().getNormalizer().normalize( oldNormValue );
2627                         ( ( Index ) userIndex ).drop( partitionTxn, normalized, id );
2628 
2629                         /*
2630                          * If there is no value for id in this index due to our
2631                          * drop above we remove the oldRdnAttr from the presence idx
2632                          */
2633                         if ( null == userIndex.reverseLookup( partitionTxn, oldId ) )
2634                         {
2635                             String oldNormTypeOid = presenceNormalizer.normalize( oldNormType );
2636                             presenceIdx.drop( partitionTxn, oldNormTypeOid, oldId );
2637                         }
2638                     }
2639                 }
2640             }
2641         }
2642 
2643         // Remove the EntryDN
2644         entry.removeAttributes( entryDnAT );
2645 
2646         setContextCsn( entry.get( entryCsnAT ).getString() );
2647 
2648         // And save the modified entry
2649         master.put( partitionTxn, oldId, entry );
2650     }
2651 
2652 
2653     /**
2654      * {@inheritDoc}
2655      */
2656     @SuppressWarnings("unchecked")
2657     @Override
2658     public final synchronized void rename( PartitionTxn partitionTxn, Dn dn, Rdn newRdn, boolean deleteOldRdn, Entry entry ) 
2659         throws LdapException
2660     {
2661         String oldId = getEntryId( partitionTxn, dn );
2662 
2663         try
2664         {
2665             rename( partitionTxn, oldId, newRdn, deleteOldRdn, entry );
2666         }
2667         catch ( IndexNotFoundException infe )
2668         {
2669             throw new LdapOtherException( infe.getMessage(), infe );
2670         }
2671 
2672         /*
2673          * H A N D L E   D N   C H A N G E
2674          * ====================================================================
2675          * We only need to update the Rdn index.
2676          * No need to calculate the new Dn.
2677          */
2678         String parentId = getParentId( partitionTxn, oldId );
2679 
2680         // Get the old parentIdAndRdn to get the nb of children and descendant
2681         ParentIdAndRdn parentIdAndRdn = rdnIdx.reverseLookup( partitionTxn, oldId );
2682 
2683         // Now we can drop it
2684         rdnIdx.drop( partitionTxn, oldId );
2685         
2686         updatePiarCache( parentIdAndRdn, oldId, DEL_CACHE );
2687 
2688         // Update the descendants
2689         parentIdAndRdn.setParentId( parentId );
2690         parentIdAndRdn.setRdns( newRdn );
2691 
2692         rdnIdx.add( partitionTxn, parentIdAndRdn, oldId );
2693 
2694         updatePiarCache( parentIdAndRdn, oldId, ADD_CACHE );
2695 
2696         entryDnCache.invalidateAll();
2697         
2698         if ( isSyncOnWrite.get() )
2699         {
2700             sync();
2701         }
2702     }
2703 
2704 
2705     //---------------------------------------------------------------------------------------------
2706     // The Unbind operation
2707     //---------------------------------------------------------------------------------------------
2708     /**
2709      * {@inheritDoc}
2710      */
2711     @Override
2712     public final void unbind( UnbindOperationContext unbindContext ) throws LdapException
2713     {
2714         // does nothing
2715     }
2716 
2717 
2718     /**
2719      * This method calls {@link Partition#lookup(LookupOperationContext)} and return <tt>true</tt>
2720      * if it returns an entry by default.  Please override this method if
2721      * there is more effective way for your implementation.
2722      */
2723     @Override
2724     public boolean hasEntry( HasEntryOperationContext entryContext ) throws LdapException
2725     {
2726         PartitionTxn partitionTxn = entryContext.getTransaction();
2727         
2728         assert ( partitionTxn != null );
2729 
2730         try
2731         {
2732             setRWLock( entryContext );
2733 
2734             String id = getEntryId( partitionTxn, entryContext.getDn() );
2735 
2736             Entry entry = fetch( partitionTxn, id, entryContext.getDn() );
2737 
2738             return entry != null;
2739         }
2740         catch ( LdapException e )
2741         {
2742             return false;
2743         }
2744     }
2745 
2746 
2747     //---------------------------------------------------------------------------------------------
2748     // Helper methods
2749     //---------------------------------------------------------------------------------------------
2750     /**
2751      * updates the CSN index
2752      *
2753      * @param partitionTxn The transaction to use
2754      * @param entry the entry having entryCSN attribute
2755      * @param id UUID of the entry
2756      * @throws Exception
2757      */
2758     private void updateCsnIndex( PartitionTxn partitionTxn, Entry entry, String id ) throws LdapException
2759     {
2760         String entryCsn = entry.get( SchemaConstants.ENTRY_CSN_AT ).getString();
2761         entryCsnIdx.drop( partitionTxn, id );
2762         entryCsnIdx.add( partitionTxn, entryCsn, id );
2763     }
2764     
2765     
2766     /**
2767      * Update the ParentIdAndRdn cache, by adding or removing an element
2768      */
2769     private void updatePiarCache( ParentIdAndRdn piar, String id, boolean add )
2770     {
2771         if ( add == ADD_CACHE )
2772         {
2773             piarCache.put( id, piar );
2774         }
2775         else
2776         {
2777             piarCache.invalidate( id );
2778         }
2779     }
2780 
2781 
2782     // ------------------------------------------------------------------------
2783     // Index and master table Operations
2784     // ------------------------------------------------------------------------
2785     /**
2786      * builds the Dn of the entry identified by the given id
2787      *
2788      * @param partitionTxn The transaction to use
2789      * @param id the entry's id
2790      * @return the normalized Dn of the entry
2791      * @throws LdapException If we can't build the entry Dn
2792      */
2793     protected Dn buildEntryDn( PartitionTxn partitionTxn, String id ) throws LdapException
2794     {
2795         String parentId = id;
2796         String rootId = Partition.ROOT_ID;
2797 
2798         // Create an array of 10 rdns, just in case. We will extend it if needed
2799         Rdn[] rdnArray = new Rdn[10];
2800         int pos = 0;
2801 
2802         Dn dn = null;
2803         
2804         try
2805         {
2806             rwLock.readLock().lock();
2807 
2808             if ( entryDnCache != null )
2809             {
2810                 Dn cachedDn = entryDnCache.getIfPresent( id );
2811                 
2812                 if ( cachedDn != null )
2813                 {
2814                     return cachedDn;
2815                 }
2816             }
2817             
2818             do
2819             {
2820                 ParentIdAndRdn cur;
2821             
2822                 if ( piarCache != null )
2823                 {
2824                     cur = piarCache.getIfPresent( parentId );
2825                     
2826                     if ( cur == null )
2827                     {
2828                         cur = rdnIdx.reverseLookup( partitionTxn, parentId );
2829                         
2830                         if ( cur == null )
2831                         {
2832                             return null;
2833                         }
2834                         
2835                         piarCache.put( parentId, cur );
2836                     }
2837                 }
2838                 else
2839                 {
2840                     cur = rdnIdx.reverseLookup( partitionTxn, parentId );
2841                     
2842                     if ( cur == null )
2843                     {
2844                         return null;
2845                     }
2846                 }
2847 
2848                 Rdn[] rdns = cur.getRdns();
2849 
2850                 for ( Rdn rdn : rdns )
2851                 {
2852                     if ( ( pos > 0 ) && ( pos % 10 == 0 ) )
2853                     {
2854                         // extend the array
2855                         Rdn[] newRdnArray = new Rdn[pos + 10];
2856                         System.arraycopy( rdnArray, 0, newRdnArray, 0, pos );
2857                         rdnArray = newRdnArray;
2858                     }
2859 
2860                     rdnArray[pos++] = rdn;
2861                 }
2862 
2863                 parentId = cur.getParentId();
2864             }
2865             while ( !parentId.equals( rootId ) );
2866             
2867             dn = new Dn( schemaManager, Arrays.copyOf( rdnArray, pos ) );
2868             
2869             entryDnCache.put( id, dn );
2870             return dn;
2871         }
2872         finally
2873         {
2874             rwLock.readLock().unlock();
2875         }
2876     }
2877 
2878 
2879     /**
2880      * {@inheritDoc}
2881      */
2882     @Override
2883     public long count( PartitionTxn partitionTxn ) throws LdapException
2884     {
2885         return master.count( partitionTxn );
2886     }
2887 
2888 
2889     /**
2890      * {@inheritDoc}
2891      */
2892     @Override
2893     public final long getChildCount( PartitionTxn partitionTxn, String id ) throws LdapException
2894     {
2895         try
2896         {
2897             ParentIdAndRdn parentIdAndRdn = rdnIdx.reverseLookup( partitionTxn, id );
2898 
2899             return parentIdAndRdn.getNbChildren();
2900         }
2901         catch ( Exception e )
2902         {
2903             throw new LdapOperationErrorException( e.getMessage(), e );
2904         }
2905     }
2906 
2907 
2908     /**
2909      * {@inheritDoc}
2910      */
2911     @Override
2912     public final Dn getEntryDn( PartitionTxn partitionTxn, String id ) throws LdapException
2913     {
2914         return buildEntryDn( partitionTxn, id );
2915     }
2916 
2917 
2918     /**
2919      * {@inheritDoc}
2920      */
2921     @Override
2922     public final String getEntryId( PartitionTxn partitionTxn, Dn dn ) throws LdapException
2923     {
2924         try
2925         {
2926             if ( Dn.isNullOrEmpty( dn ) )
2927             {
2928                 return Partition.ROOT_ID;
2929             }
2930 
2931             ParentIdAndRdn/xdbm/ParentIdAndRdn.html#ParentIdAndRdn">ParentIdAndRdn suffixKey = new ParentIdAndRdn( Partition.ROOT_ID, suffixDn.getRdns() );
2932 
2933             // Check into the Rdn index, starting with the partition Suffix
2934             try
2935             {
2936                 rwLock.readLock().lock();
2937                 String currentId = rdnIdx.forwardLookup( partitionTxn, suffixKey );
2938 
2939                 for ( int i = dn.size() - suffixDn.size(); i > 0; i-- )
2940                 {
2941                     Rdn rdn = dn.getRdn( i - 1 );
2942                     ParentIdAndRdnxdbm/ParentIdAndRdn.html#ParentIdAndRdn">ParentIdAndRdn currentRdn = new ParentIdAndRdn( currentId, rdn );
2943                     
2944                     currentId = rdnIdx.forwardLookup( partitionTxn, currentRdn );
2945 
2946                     if ( currentId == null )
2947                     {
2948                         break;
2949                     }
2950                 }
2951 
2952                 return currentId;
2953             }
2954             finally
2955             {
2956                 rwLock.readLock().unlock();
2957             }
2958         }
2959         catch ( Exception e )
2960         {
2961             throw new LdapException( e.getMessage(), e );
2962         }
2963     }
2964 
2965 
2966     /**
2967      * {@inheritDoc}
2968      */
2969     @Override
2970     public String getParentId( PartitionTxn partitionTxn, String childId ) throws LdapException
2971     {
2972         try
2973         {
2974             rwLock.readLock().lock();
2975             ParentIdAndRdn key = rdnIdx.reverseLookup( partitionTxn, childId );
2976 
2977             if ( key == null )
2978             {
2979                 return null;
2980             }
2981 
2982             return key.getParentId();
2983         }
2984         finally
2985         {
2986             rwLock.readLock().unlock();
2987         }
2988     }
2989 
2990 
2991     /**
2992      * Retrieve the SuffixID
2993      * 
2994      * @param partitionTxn The transaction to use
2995      * @return The Suffix ID
2996      * @throws LdapException If we weren't able to retrieve the Suffix ID
2997      */
2998     public String getSuffixId( PartitionTxn partitionTxn ) throws LdapException
2999     {
3000         if ( suffixId == null )
3001         {
3002             ParentIdAndRdnserver/xdbm/ParentIdAndRdn.html#ParentIdAndRdn">ParentIdAndRdn key = new ParentIdAndRdn( Partition.ROOT_ID, suffixDn.getRdns() );
3003 
3004             try
3005             {
3006                 rwLock.readLock().lock();
3007                 suffixId = rdnIdx.forwardLookup( partitionTxn, key );
3008             }
3009             finally
3010             {
3011                 rwLock.readLock().unlock();
3012             }
3013         }
3014 
3015         return suffixId;
3016     }
3017 
3018 
3019     //------------------------------------------------------------------------
3020     // Index handling
3021     //------------------------------------------------------------------------
3022     /**
3023      * {@inheritDoc}
3024      */
3025     @Override
3026     public void addIndex( Index<?, String> index ) throws LdapException
3027     {
3028         checkInitialized( "addIndex" );
3029 
3030         // Check that the index String is valid
3031         AttributeType attributeType = null;
3032 
3033         try
3034         {
3035             attributeType = schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() );
3036         }
3037         catch ( LdapNoSuchAttributeException lnsae )
3038         {
3039             LOG.error( "Cannot initialize the index for AttributeType {}, this value does not exist",
3040                 index.getAttributeId() );
3041 
3042             return;
3043         }
3044 
3045         String oid = attributeType.getOid();
3046 
3047         if ( SYS_INDEX_OIDS.contains( oid ) )
3048         {
3049             if ( !systemIndices.containsKey( oid ) )
3050             {
3051                 systemIndices.put( oid, index );
3052             }
3053         }
3054         else
3055         {
3056             if ( !userIndices.containsKey( oid ) )
3057             {
3058                 userIndices.put( oid, index );
3059             }
3060         }
3061     }
3062 
3063 
3064     /**
3065      * Add some new indexes
3066      * @param indexes The added indexes
3067      */
3068     public void addIndexedAttributes( Index<?, String>... indexes )
3069     {
3070         for ( Index<?, String> index : indexes )
3071         {
3072             indexedAttributes.add( index );
3073         }
3074     }
3075 
3076 
3077     /**
3078      * Set the list of indexes for this partition
3079      * @param indexedAttributes The list of indexes
3080      */
3081     public void setIndexedAttributes( Set<Index<?, String>> indexedAttributes )
3082     {
3083         this.indexedAttributes = indexedAttributes;
3084     }
3085 
3086 
3087     /**
3088      * @return The list of indexed attributes
3089      */
3090     public Set<Index<?, String>> getIndexedAttributes()
3091     {
3092         return indexedAttributes;
3093     }
3094 
3095 
3096     /**
3097      * {@inheritDoc}
3098      */
3099     @Override
3100     public Iterator<String> getUserIndices()
3101     {
3102         return userIndices.keySet().iterator();
3103     }
3104 
3105 
3106     /**
3107      * {@inheritDoc}
3108      */
3109     @Override
3110     public Iterator<String> getSystemIndices()
3111     {
3112         return systemIndices.keySet().iterator();
3113     }
3114 
3115 
3116     /**
3117      * {@inheritDoc}
3118      */
3119     @Override
3120     public Index<?, String> getIndex( AttributeType attributeType ) throws IndexNotFoundException
3121     {
3122         String id = attributeType.getOid();
3123 
3124         if ( userIndices.containsKey( id ) )
3125         {
3126             return userIndices.get( id );
3127         }
3128 
3129         if ( systemIndices.containsKey( id ) )
3130         {
3131             return systemIndices.get( id );
3132         }
3133 
3134         throw new IndexNotFoundException( I18n.err( I18n.ERR_3, id, id ) );
3135     }
3136 
3137 
3138     /**
3139      * {@inheritDoc}
3140      */
3141     @Override
3142     public Index<?, String> getUserIndex( AttributeType attributeType ) throws IndexNotFoundException
3143     {
3144         if ( attributeType == null )
3145         {
3146             throw new IndexNotFoundException( I18n.err( I18n.ERR_3, attributeType, attributeType ) );
3147         }
3148 
3149         String oid = attributeType.getOid();
3150 
3151         if ( userIndices.containsKey( oid ) )
3152         {
3153             return userIndices.get( oid );
3154         }
3155 
3156         throw new IndexNotFoundException( I18n.err( I18n.ERR_3, attributeType, attributeType ) );
3157     }
3158 
3159 
3160     /**
3161      * {@inheritDoc}
3162      */
3163     @Override
3164     public Index<?, String> getSystemIndex( AttributeType attributeType ) throws IndexNotFoundException
3165     {
3166         if ( attributeType == null )
3167         {
3168             throw new IndexNotFoundException( I18n.err( I18n.ERR_2, attributeType, attributeType ) );
3169         }
3170 
3171         String oid = attributeType.getOid();
3172 
3173         if ( systemIndices.containsKey( oid ) )
3174         {
3175             return systemIndices.get( oid );
3176         }
3177 
3178         throw new IndexNotFoundException( I18n.err( I18n.ERR_2, attributeType, attributeType ) );
3179     }
3180 
3181 
3182     /**
3183      * {@inheritDoc}
3184      */
3185     @SuppressWarnings("unchecked")
3186     @Override
3187     public Index<Dn, String> getAliasIndex()
3188     {
3189         return ( Index<Dn, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_ALIAS_AT_OID );
3190     }
3191 
3192 
3193     /**
3194      * {@inheritDoc}
3195      */
3196     @SuppressWarnings("unchecked")
3197     @Override
3198     public Index<String, String> getOneAliasIndex()
3199     {
3200         return ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID );
3201     }
3202 
3203 
3204     /**
3205      * {@inheritDoc}
3206      */
3207     @SuppressWarnings("unchecked")
3208     @Override
3209     public Index<String, String> getSubAliasIndex()
3210     {
3211         return ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID );
3212     }
3213 
3214 
3215     /**
3216      * {@inheritDoc}
3217      */
3218     @SuppressWarnings("unchecked")
3219     @Override
3220     public Index<String, String> getObjectClassIndex()
3221     {
3222         return ( Index<String, String> ) systemIndices.get( SchemaConstants.OBJECT_CLASS_AT_OID );
3223     }
3224 
3225 
3226     /**
3227      * {@inheritDoc}
3228      */
3229     @SuppressWarnings("unchecked")
3230     @Override
3231     public Index<String, String> getEntryCsnIndex()
3232     {
3233         return ( Index<String, String> ) systemIndices.get( SchemaConstants.ENTRY_CSN_AT_OID );
3234     }
3235 
3236 
3237     /**
3238      * {@inheritDoc}
3239      */
3240     @SuppressWarnings("unchecked")
3241     public Index<String, String> getAdministrativeRoleIndex()
3242     {
3243         return ( Index<String, String> ) systemIndices.get( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID );
3244     }
3245 
3246 
3247     /**
3248      * {@inheritDoc}
3249      */
3250     @SuppressWarnings("unchecked")
3251     @Override
3252     public Index<String, String> getPresenceIndex()
3253     {
3254         return ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_PRESENCE_AT_OID );
3255     }
3256 
3257 
3258     /**
3259      * {@inheritDoc}
3260      */
3261     @SuppressWarnings("unchecked")
3262     @Override
3263     public Index<ParentIdAndRdn, String> getRdnIndex()
3264     {
3265         return ( Index<ParentIdAndRdn, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_RDN_AT_OID );
3266     }
3267 
3268 
3269     /**
3270      * {@inheritDoc}
3271      */
3272     @Override
3273     public boolean hasUserIndexOn( AttributeType attributeType ) throws LdapException
3274     {
3275         String oid = attributeType.getOid();
3276         return userIndices.containsKey( oid );
3277     }
3278 
3279 
3280     /**
3281      * {@inheritDoc}
3282      */
3283     @Override
3284     public boolean hasSystemIndexOn( AttributeType attributeType ) throws LdapException
3285     {
3286         return systemIndices.containsKey( attributeType.getOid() );
3287     }
3288 
3289 
3290     /**
3291      * {@inheritDoc}
3292      */
3293     @Override
3294     public boolean hasIndexOn( AttributeType attributeType ) throws LdapException
3295     {
3296         return hasUserIndexOn( attributeType ) || hasSystemIndexOn( attributeType );
3297     }
3298 
3299 
3300     //---------------------------------------------------------------------------------------------
3301     // Alias index manipulation
3302     //---------------------------------------------------------------------------------------------
3303     /**
3304      * Adds userIndices for an aliasEntry to be added to the database while checking
3305      * for constrained alias constructs like alias cycles and chaining.
3306      *
3307      * @param partitionTxn The transaction to use
3308      * @param aliasDn normalized distinguished name for the alias entry
3309      * @param aliasTarget the user provided aliased entry dn as a string
3310      * @param aliasId the id of alias entry to add
3311      * @throws LdapException if index addition fails, and if the alias is
3312      * not allowed due to chaining or cycle formation.
3313      * @throws LdapException if the wrappedCursor btrees cannot be altered
3314      */
3315     protected void addAliasIndices( PartitionTxn partitionTxn, String aliasId, Dn aliasDn, Dn aliasTarget ) 
3316             throws LdapException
3317     {
3318         String targetId; // Id of the aliasedObjectName
3319         Dn ancestorDn; // Name of an alias entry relative
3320         String ancestorId; // Id of an alias entry relative
3321 
3322         /*
3323          * Check For Aliases External To Naming Context
3324          *
3325          * id may be null but the alias may be to a valid entry in
3326          * another namingContext.  Such aliases are not allowed and we
3327          * need to point it out to the user instead of saying the target
3328          * does not exist when it potentially could outside of this upSuffix.
3329          */
3330         if ( !aliasTarget.isDescendantOf( suffixDn ) )
3331         {
3332             String msg = I18n.err( I18n.ERR_225, suffixDn.getName() );
3333             throw new LdapAliasDereferencingException( msg );
3334         }
3335 
3336         // L O O K U P   T A R G E T   I D
3337         targetId = getEntryId( partitionTxn, aliasTarget );
3338 
3339         /*
3340          * Check For Target Existence
3341          *
3342          * We do not allow the creation of inconsistent aliases.  Aliases should
3343          * not be broken links.  If the target does not exist we start screaming
3344          */
3345         if ( null == targetId )
3346         {
3347             // Complain about target not existing
3348             String msg = I18n.err( I18n.ERR_581, aliasDn.getName(), aliasTarget );
3349             throw new LdapAliasException( msg );
3350         }
3351 
3352         /*
3353          * Detect Direct Alias Chain Creation
3354          *
3355          * Rather than resusitate the target to test if it is an alias and fail
3356          * due to chaing creation we use the alias index to determine if the
3357          * target is an alias.  Hence if the alias we are about to create points
3358          * to another alias as its target in the aliasedObjectName attribute,
3359          * then we have a situation where an alias chain is being created.
3360          * Alias chaining is not allowed so we throw and exception.
3361          */
3362         if ( null != aliasIdx.reverseLookup( partitionTxn, targetId ) )
3363         {
3364             String msg = I18n.err( I18n.ERR_227 );
3365             throw new LdapAliasDereferencingException( msg );
3366         }
3367 
3368         // Add the alias to the simple alias index
3369         aliasIdx.add( partitionTxn, aliasTarget, aliasId );
3370         
3371         if ( aliasCache != null )
3372         {
3373             aliasCache.put( aliasId, aliasTarget );
3374         }
3375 
3376         /*
3377          * Handle One Level Scope Alias Index
3378          *
3379          * The first relative is special with respect to the one level alias
3380          * index.  If the target is not a sibling of the alias then we add the
3381          * index entry maping the parent's id to the aliased target id.
3382          */
3383         ancestorDn = aliasDn.getParent();
3384         ancestorId = getEntryId( partitionTxn, ancestorDn );
3385 
3386         // check if alias parent and aliased entry are the same
3387         Dn normalizedAliasTargetParentDn = aliasTarget.getParent();
3388 
3389         if ( !aliasDn.isDescendantOf( normalizedAliasTargetParentDn ) )
3390         {
3391             oneAliasIdx.add( partitionTxn, ancestorId, targetId );
3392         }
3393 
3394         /*
3395          * Handle Sub Level Scope Alias Index
3396          *
3397          * Walk the list of relatives from the parents up to the upSuffix, testing
3398          * to see if the alias' target is a descendant of the relative.  If the
3399          * alias target is not a descentant of the relative it extends the scope
3400          * and is added to the sub tree scope alias index.  The upSuffix node is
3401          * ignored since everything is under its scope.  The first loop
3402          * iteration shall handle the parents.
3403          */
3404         while ( !ancestorDn.equals( suffixDn ) && null != ancestorId )
3405         {
3406             if ( !aliasTarget.isDescendantOf( ancestorDn ) )
3407             {
3408                 subAliasIdx.add( partitionTxn, ancestorId, targetId );
3409             }
3410 
3411             ancestorDn = ancestorDn.getParent();
3412             ancestorId = getEntryId( partitionTxn, ancestorDn );
3413         }
3414     }
3415 
3416 
3417     /**
3418      * Removes the index entries for an alias before the entry is deleted from
3419      * the master table.
3420      *
3421      * TODO Optimize this by walking the hierarchy index instead of the name
3422      * 
3423      * @param partitionTxn The transaction to use
3424      * @param aliasId the id of the alias entry in the master table
3425      * @throws LdapException if we cannot delete index values in the database
3426      */
3427     protected void dropAliasIndices( PartitionTxn partitionTxn, String aliasId ) throws LdapException
3428     {
3429         Dn targetDn = aliasIdx.reverseLookup( partitionTxn, aliasId );
3430         
3431         if ( !targetDn.isSchemaAware() )
3432         {
3433             targetDn = new Dn( schemaManager, targetDn );
3434         }
3435 
3436         String targetId = getEntryId( partitionTxn, targetDn );
3437 
3438         if ( targetId == null )
3439         {
3440             // the entry doesn't exist, probably it has been deleted or renamed
3441             // TODO: this is just a workaround for now, the alias indices should be updated when target entry is deleted or removed
3442             return;
3443         }
3444 
3445         Dn aliasDn = getEntryDn( partitionTxn, aliasId );
3446 
3447         Dn ancestorDn = aliasDn.getParent();
3448         String ancestorId = getEntryId( partitionTxn, ancestorDn );
3449 
3450         /*
3451          * We cannot just drop all tuples in the one level and subtree userIndices
3452          * linking baseIds to the targetId.  If more than one alias refers to
3453          * the target then droping all tuples with a value of targetId would
3454          * make all other aliases to the target inconsistent.
3455          *
3456          * We need to walk up the path of alias ancestors until we reach the
3457          * upSuffix, deleting each ( ancestorId, targetId ) tuple in the
3458          * subtree scope alias.  We only need to do this for the direct parent
3459          * of the alias on the one level subtree.
3460          */
3461         oneAliasIdx.drop( partitionTxn, ancestorId, targetId );
3462         subAliasIdx.drop( partitionTxn, ancestorId, targetId );
3463 
3464         while ( !ancestorDn.equals( suffixDn ) && ancestorDn.size() > suffixDn.size() )
3465         {
3466             ancestorDn = ancestorDn.getParent();
3467             ancestorId = getEntryId( partitionTxn, ancestorDn );
3468 
3469             subAliasIdx.drop( partitionTxn, ancestorId, targetId );
3470         }
3471 
3472         // Drops all alias tuples pointing to the id of the alias to be deleted
3473         aliasIdx.drop( partitionTxn, aliasId );
3474 
3475         if ( aliasCache != null )
3476         {
3477             aliasCache.invalidate( aliasId );
3478         }
3479     }
3480 
3481 
3482     /**
3483      * For all aliases including and under the moved base, this method removes
3484      * one and subtree alias index tuples for old ancestors above the moved base
3485      * that will no longer be ancestors after the move.
3486      *
3487      * @param partitionTxn The transaction to use
3488      * @param movedBase the base at which the move occurred - the moved node
3489      * @throws LdapException if system userIndices fail
3490      */
3491     protected void dropMovedAliasIndices( PartitionTxn partitionTxn, Dn movedBase ) throws LdapException
3492     {
3493         String movedBaseId = getEntryId( partitionTxn, movedBase );
3494 
3495         Dn targetDn = aliasIdx.reverseLookup( partitionTxn, movedBaseId );
3496         
3497         if ( targetDn != null )
3498         {
3499             if ( !targetDn.isSchemaAware() )
3500             {
3501                 targetDn = new Dn( schemaManager, targetDn );
3502             }
3503 
3504             String targetId = getEntryId( partitionTxn, targetDn );
3505             Dn aliasDn = getEntryDn( partitionTxn, movedBaseId );
3506 
3507             /*
3508              * Start droping index tuples with the first ancestor right above the
3509              * moved base.  This is the first ancestor effected by the move.
3510              */
3511             Dn ancestorDn = movedBase.getParent();
3512             String ancestorId = getEntryId( partitionTxn, ancestorDn );
3513 
3514             /*
3515              * We cannot just drop all tuples in the one level and subtree userIndices
3516              * linking baseIds to the targetId.  If more than one alias refers to
3517              * the target then droping all tuples with a value of targetId would
3518              * make all other aliases to the target inconsistent.
3519              *
3520              * We need to walk up the path of alias ancestors right above the moved
3521              * base until we reach the upSuffix, deleting each ( ancestorId,
3522              * targetId ) tuple in the subtree scope alias.  We only need to do
3523              * this for the direct parent of the alias on the one level subtree if
3524              * the moved base is the alias.
3525              */
3526             if ( aliasDn.equals( movedBase ) )
3527             {
3528                 oneAliasIdx.drop( partitionTxn, ancestorId, targetId );
3529             }
3530 
3531             subAliasIdx.drop( partitionTxn, ancestorId, targetId );
3532 
3533             while ( !ancestorDn.equals( suffixDn ) )
3534             {
3535                 ancestorDn = ancestorDn.getParent();
3536                 ancestorId = getEntryId( partitionTxn, ancestorDn );
3537 
3538                 subAliasIdx.drop( partitionTxn, ancestorId, targetId );
3539             }
3540         }
3541     }
3542 
3543 
3544     //---------------------------------------------------------------------------------------------
3545     // Debug methods
3546     //---------------------------------------------------------------------------------------------
3547     private void dumpIndex( PartitionTxn partitionTxn, OutputStream stream, Index<?, String> index )
3548     {
3549         try
3550         {
3551             Cursor<IndexEntry<?, String>> cursor = ( Cursor ) index.forwardCursor( partitionTxn );
3552 
3553             while ( cursor.next() )
3554             {
3555                 IndexEntry<?, String> entry = cursor.get();
3556 
3557                 System.out.println( entry );
3558             }
3559         }
3560         catch ( Exception e )
3561         {
3562             // TODO : fixme
3563         }
3564     }
3565 
3566 
3567     /**
3568      * {@inheritDoc}
3569      */
3570     @Override
3571     public void dumpIndex( PartitionTxn partitionTxn, OutputStream stream, String name ) throws IOException
3572     {
3573         try
3574         {
3575             AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( name );
3576 
3577             if ( attributeType == null )
3578             {
3579                 stream.write( Strings.getBytesUtf8( "Cannot find an index for AttributeType names " + name ) );
3580 
3581                 return;
3582             }
3583 
3584             if ( attributeType.getOid().equals( ApacheSchemaConstants.APACHE_RDN_AT_OID ) )
3585             {
3586                 dumpIndex( partitionTxn, stream, rdnIdx );
3587             }
3588         }
3589         catch ( LdapException le )
3590         {
3591             stream.write( Strings.getBytesUtf8( "Cannot find an index for AttributeType names " + name ) );
3592         }
3593     }
3594 
3595 
3596     /**
3597      * {@inheritDoc}
3598      */
3599     @Override
3600     public String toString()
3601     {
3602         return "Partition<" + id + ">";
3603     }
3604 
3605 
3606     /**
3607      * Create a new Index for a given OID
3608      * 
3609      * @param indexOid The Attribute OID
3610      * @param path The working directory where this index will be stored
3611      * @param withReverse If the Reverse index must be created or not
3612      * @return The created index
3613      * @throws LdapException If the index can't be created
3614      */
3615     protected abstract Index createSystemIndex( String indexOid, URI path, boolean withReverse ) throws LdapException;
3616 
3617 
3618     /**
3619      * {@inheritDoc}
3620      */
3621     @Override
3622     public MasterTable getMasterTable()
3623     {
3624         return master;
3625     }
3626 
3627 
3628     /**
3629      * Acquire a Read lock
3630      */
3631     private void lockRead()
3632     {
3633         rwLock.readLock().lock();
3634     }
3635 
3636 
3637     /**
3638      * Release a Read lock
3639      */
3640     private void unlockRead()
3641     {
3642         rwLock.readLock().unlock();
3643     }
3644 
3645 
3646     /**
3647      * Acquire a Write lock
3648      */
3649     private void lockWrite()
3650     {
3651         rwLock.writeLock().lock();
3652     }
3653 
3654 
3655     /**
3656      * Release a Write lock
3657      */
3658     private void unlockWrite()
3659     {
3660         rwLock.writeLock().unlock();
3661     }
3662 
3663 
3664     /**
3665      * updates the cache based on the type of OperationContext
3666      * 
3667      * @param opCtx the operation's context
3668      */
3669     public void updateCache( OperationContext opCtx )
3670     {
3671         // partition implementations should override this if they want to use cache
3672     }
3673 
3674 
3675     /**
3676      * looks up for the entry with the given ID in the cache
3677      *
3678      * @param id the ID of the entry
3679      * @return the Entry if exists, null otherwise
3680      */
3681     public Entry lookupCache( String id )
3682     {
3683         return null;
3684     }
3685 
3686 
3687     /**
3688      * adds the given entry to cache
3689      *  
3690      * Note: this method is not called during add operation to avoid filling the cache
3691      *       with all the added entries
3692      *       
3693      * @param id ID of the entry
3694      * @param entry the Entry
3695      */
3696     public void addToCache( String id, Entry entry )
3697     {
3698     }
3699 
3700 
3701     /**
3702      * @return the optimizer
3703      */
3704     public Optimizer getOptimizer()
3705     {
3706         return optimizer;
3707     }
3708 
3709 
3710     /**
3711      * @param optimizer the optimizer to set
3712      */
3713     public void setOptimizer( Optimizer optimizer )
3714     {
3715         this.optimizer = optimizer;
3716     }
3717 
3718 
3719     /**
3720      * @param searchEngine the searchEngine to set
3721      */
3722     public void setSearchEngine( SearchEngine searchEngine )
3723     {
3724         this.searchEngine = searchEngine;
3725     }
3726 
3727 
3728     /**
3729      * Set and return the ReadWrite lock we use to protect the backend against concurrent modifications
3730      * 
3731      * @param operationContext The OperationContext which contain the reference to the OperationManager
3732      */
3733     private void setRWLock( OperationContext operationContext )
3734     {
3735         if ( operationContext.getSession() != null )
3736         {
3737             rwLock = operationContext.getSession().getDirectoryService().getOperationManager().getRWLock();
3738         }
3739         else
3740         {
3741             if ( rwLock == null )
3742             {
3743                 // Create a ReadWrite lock from scratch
3744                 rwLock = new ReentrantReadWriteLock();
3745             }
3746         }
3747     }
3748 
3749 
3750     /**
3751      * {@inheritDoc}
3752      */
3753     @Override
3754     public ReadWriteLock getReadWriteLock()
3755     {
3756         return rwLock;
3757     }
3758 
3759     
3760     /**
3761      * {@inheritDoc}
3762      */
3763     @Override
3764     public Cache<String, Dn> getAliasCache()
3765     {
3766         return aliasCache;
3767     }
3768     
3769     
3770     /**
3771      * {@inheritDoc}
3772      */
3773     @Override
3774     public String getContextCsn( PartitionTxn partitionTxn )
3775     {
3776         if ( super.getContextCsn( partitionTxn ) == null )
3777         {
3778            loadContextCsn( partitionTxn ); 
3779         }
3780         
3781         return super.getContextCsn( partitionTxn );
3782     }
3783 
3784 
3785     /**
3786      * Loads the current context CSN present in the context entry of the partition
3787      *
3788      * @param partitionTxn The transaction to use
3789      */
3790     protected void loadContextCsn( PartitionTxn partitionTxn )
3791     {
3792         try
3793         {
3794             if ( rwLock == null )
3795             {
3796                 // Create a ReadWrite lock from scratch
3797                 rwLock = new ReentrantReadWriteLock();
3798             }
3799 
3800             // load the last stored valid CSN value
3801             String contextEntryId = getEntryId( partitionTxn, getSuffixDn() );
3802             
3803             if ( contextEntryId == null )
3804             {
3805                 return;
3806             }
3807             
3808             Entry entry = fetch( partitionTxn, contextEntryId );
3809             
3810             Attribute ctxCsnAt = entry.get( contextCsnAT );
3811             
3812             if ( ctxCsnAt != null )
3813             {
3814                 setContextCsn( ctxCsnAt.getString() );
3815                 ctxCsnChanged = false; // this is just loaded, not new
3816             }
3817         }
3818         catch ( LdapException e )
3819         {
3820             throw new RuntimeException( e );
3821         }
3822     }
3823     
3824     
3825     /**
3826      * {@inheritDoc}
3827      */
3828     // store the contextCSN value in the context entry 
3829     // note that this modification shouldn't change the entryCSN value of the context entry
3830     @Override
3831     public void saveContextCsn( PartitionTxn partitionTxn ) throws LdapException
3832     {
3833         if ( !ctxCsnChanged )
3834         {
3835             return;
3836         }
3837         
3838         String contextCsn = super.getContextCsn( partitionTxn );
3839         
3840         if ( contextCsn == null )
3841         {
3842             return;
3843         }
3844         
3845         try
3846         {
3847             // we don't need to use the ctxCsnSemaphore here cause
3848             // the only other place this is called is from PartitionNexus.sync()
3849             // but that is protected by write lock in DefaultDirectoryService.shutdown()
3850             
3851             String contextEntryId = getEntryId( partitionTxn, getSuffixDn() );
3852             Entry origEntry = fetch( partitionTxn, contextEntryId );
3853             
3854             // The Context Entry may have been deleted. Get out if we don't find it
3855             if ( origEntry == null )
3856             {
3857                 return;
3858             }
3859 
3860             origEntry = ( ( ClonedServerEntry ) origEntry ).getOriginalEntry();
3861             
3862             origEntry.removeAttributes( contextCsnAT, entryDnAT );
3863             
3864             origEntry.add( contextCsnAT, contextCsn );
3865             
3866             master.put( partitionTxn, contextEntryId, origEntry );
3867             
3868             ctxCsnChanged = false;
3869             
3870             LOG.debug( "Saved context CSN {} for the partition {}", contextCsn, suffixDn );
3871         }
3872         catch ( Exception e )
3873         {
3874             throw new LdapOperationErrorException( e.getMessage(), e );
3875         }
3876     }
3877     
3878     
3879     /**
3880      * {@inheritDoc}
3881      */
3882     @Override
3883     public Subordinates getSubordinates( PartitionTxn partitionTxn, Entry entry ) throws LdapException
3884     {
3885         Subordinatescore/api/partition/Subordinates.html#Subordinates">Subordinates subordinates = new Subordinates();
3886         
3887         try
3888         {
3889             // Check into the Rdn index, starting with the partition Suffix
3890             try
3891             {
3892                 rwLock.readLock().lock();
3893                 ParentIdAndRdn parentIdAndRdn = rdnIdx.reverseLookup( partitionTxn, entry.get( SchemaConstants.ENTRY_UUID_AT ).getString() );
3894 
3895                 subordinates.setNbChildren( parentIdAndRdn.getNbChildren() );
3896                 subordinates.setNbSubordinates( parentIdAndRdn.getNbDescendants() );
3897             }
3898             finally
3899             {
3900                 rwLock.readLock().unlock();
3901             }
3902         }
3903         catch ( Exception e )
3904         {
3905             throw new LdapException( e.getMessage(), e );
3906         }
3907 
3908         return subordinates;
3909     }
3910 }