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