001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.server.core.partition.impl.btree;
021
022
023import java.io.IOException;
024import java.io.OutputStream;
025import java.net.URI;
026import java.time.Duration;
027import java.util.Arrays;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.concurrent.Semaphore;
035import java.util.concurrent.atomic.AtomicBoolean;
036import java.util.concurrent.locks.ReadWriteLock;
037import java.util.concurrent.locks.ReentrantReadWriteLock;
038
039import org.apache.directory.api.ldap.model.constants.SchemaConstants;
040import org.apache.directory.api.ldap.model.cursor.Cursor;
041import org.apache.directory.api.ldap.model.cursor.CursorException;
042import org.apache.directory.api.ldap.model.entry.Attribute;
043import org.apache.directory.api.ldap.model.entry.Entry;
044import org.apache.directory.api.ldap.model.entry.Modification;
045import org.apache.directory.api.ldap.model.entry.Value;
046import org.apache.directory.api.ldap.model.exception.LdapAliasDereferencingException;
047import org.apache.directory.api.ldap.model.exception.LdapAliasException;
048import org.apache.directory.api.ldap.model.exception.LdapContextNotEmptyException;
049import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException;
050import org.apache.directory.api.ldap.model.exception.LdapException;
051import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
052import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException;
053import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException;
054import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
055import org.apache.directory.api.ldap.model.exception.LdapOtherException;
056import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException;
057import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
058import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
059import org.apache.directory.api.ldap.model.name.Ava;
060import org.apache.directory.api.ldap.model.name.Dn;
061import org.apache.directory.api.ldap.model.name.Rdn;
062import org.apache.directory.api.ldap.model.schema.AttributeType;
063import org.apache.directory.api.ldap.model.schema.MatchingRule;
064import org.apache.directory.api.ldap.model.schema.Normalizer;
065import org.apache.directory.api.ldap.model.schema.SchemaManager;
066import org.apache.directory.api.util.Strings;
067import org.apache.directory.api.util.exception.MultiException;
068import org.apache.directory.server.constants.ApacheSchemaConstants;
069import org.apache.directory.server.core.api.DnFactory;
070import org.apache.directory.server.core.api.entry.ClonedServerEntry;
071import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
072import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
073import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
074import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
075import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
076import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
077import org.apache.directory.server.core.api.interceptor.context.ModDnAva;
078import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
079import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
080import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
081import org.apache.directory.server.core.api.interceptor.context.OperationContext;
082import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
083import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
084import org.apache.directory.server.core.api.interceptor.context.UnbindOperationContext;
085import org.apache.directory.server.core.api.partition.AbstractPartition;
086import org.apache.directory.server.core.api.partition.Partition;
087import org.apache.directory.server.core.api.partition.PartitionTxn;
088import org.apache.directory.server.core.api.partition.PartitionWriteTxn;
089import org.apache.directory.server.core.api.partition.Subordinates;
090import org.apache.directory.server.i18n.I18n;
091import org.apache.directory.server.xdbm.Index;
092import org.apache.directory.server.xdbm.IndexEntry;
093import org.apache.directory.server.xdbm.IndexNotFoundException;
094import org.apache.directory.server.xdbm.MasterTable;
095import org.apache.directory.server.xdbm.ParentIdAndRdn;
096import org.apache.directory.server.xdbm.Store;
097import org.apache.directory.server.xdbm.search.Optimizer;
098import org.apache.directory.server.xdbm.search.PartitionSearchResult;
099import org.apache.directory.server.xdbm.search.SearchEngine;
100import org.slf4j.Logger;
101import org.slf4j.LoggerFactory;
102
103import com.github.benmanes.caffeine.cache.Cache;
104import 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 */
112public 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 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                    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            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        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}