directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From elecha...@apache.org
Subject svn commit: r1149541 - in /directory/apacheds/trunk: jdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/jdbm/ xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/avl/ xdbm-partition/src/main/java...
Date Fri, 22 Jul 2011 10:10:22 GMT
Author: elecharny
Date: Fri Jul 22 10:10:21 2011
New Revision: 1149541

URL: http://svn.apache.org/viewvc?rev=1149541&view=rev
Log:
o Removed the AbstractXdbmPartition, as suggested by Stefan
o Added some Javadoc
o Added a doInit() method in the AbstractBTreePartiton, to setup the indexes

Removed:
    directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/xdbm/
Modified:
    directory/apacheds/trunk/jdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/jdbm/JdbmPartition.java
    directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/avl/AvlPartition.java
    directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/AbstractBTreePartition.java

Modified: directory/apacheds/trunk/jdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/jdbm/JdbmPartition.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/jdbm/JdbmPartition.java?rev=1149541&r1=1149540&r2=1149541&view=diff
==============================================================================
--- directory/apacheds/trunk/jdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/jdbm/JdbmPartition.java (original)
+++ directory/apacheds/trunk/jdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/jdbm/JdbmPartition.java Fri Jul 22 10:10:21 2011
@@ -34,7 +34,7 @@ import jdbm.recman.CacheRecordManager;
 
 import org.apache.directory.server.constants.ApacheSchemaConstants;
 import org.apache.directory.server.core.partition.Partition;
-import org.apache.directory.server.core.partition.impl.xdbm.AbstractXdbmPartition;
+import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
 import org.apache.directory.server.i18n.I18n;
 import org.apache.directory.server.xdbm.Index;
 import org.apache.directory.server.xdbm.search.impl.CursorBuilder;
@@ -60,7 +60,7 @@ import org.slf4j.LoggerFactory;
  *
  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
  */
-public class JdbmPartition extends AbstractXdbmPartition<Long>
+public class JdbmPartition extends AbstractBTreePartition<Long>
 {
     /** static logger */
     private static final Logger LOG = LoggerFactory.getLogger( JdbmPartition.class );
@@ -123,8 +123,7 @@ public class JdbmPartition extends Abstr
             getPartitionDir().mkdirs();
     
             // Initialize the indexes
-            setupSystemIndices();
-            setupUserIndices();
+            super.doInit();
     
             // First, check if the file storing the data exists
             String path = getPartitionDir().getPath() + File.separator + "master";

Modified: directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/avl/AvlPartition.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/avl/AvlPartition.java?rev=1149541&r1=1149540&r2=1149541&view=diff
==============================================================================
--- directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/avl/AvlPartition.java (original)
+++ directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/avl/AvlPartition.java Fri Jul 22 10:10:21 2011
@@ -23,8 +23,8 @@ package org.apache.directory.server.core
 import java.net.URI;
 
 import org.apache.directory.server.constants.ApacheSchemaConstants;
+import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
 import org.apache.directory.server.core.partition.impl.btree.LongComparator;
-import org.apache.directory.server.core.partition.impl.xdbm.AbstractXdbmPartition;
 import org.apache.directory.server.xdbm.Index;
 import org.apache.directory.server.xdbm.impl.avl.AvlIndex;
 import org.apache.directory.server.xdbm.impl.avl.AvlMasterTable;
@@ -45,7 +45,7 @@ import org.slf4j.LoggerFactory;
  *
  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
  */
-public class AvlPartition extends AbstractXdbmPartition<Long>
+public class AvlPartition extends AbstractBTreePartition<Long>
 {
     /** static logger */
     private static final Logger LOG = LoggerFactory.getLogger( AvlPartition.class );
@@ -90,8 +90,7 @@ public class AvlPartition extends Abstra
             // Create the master table (the table containing all the entries)
             master = new AvlMasterTable<Entry>( id, new LongComparator(), null, false );
     
-            setupSystemIndices();
-            setupUserIndices();
+            super.doInit();
         }
     }
 

Modified: directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/AbstractBTreePartition.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/AbstractBTreePartition.java?rev=1149541&r1=1149540&r2=1149541&view=diff
==============================================================================
--- directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/AbstractBTreePartition.java (original)
+++ directory/apacheds/trunk/xdbm-partition/src/main/java/org/apache/directory/server/core/partition/impl/btree/AbstractBTreePartition.java Fri Jul 22 10:10:21 2011
@@ -23,9 +23,11 @@ package org.apache.directory.server.core
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.URI;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -62,6 +64,7 @@ import org.apache.directory.server.xdbm.
 import org.apache.directory.server.xdbm.search.Optimizer;
 import org.apache.directory.server.xdbm.search.SearchEngine;
 import org.apache.directory.shared.ldap.model.constants.SchemaConstants;
+import org.apache.directory.shared.ldap.model.cursor.Cursor;
 import org.apache.directory.shared.ldap.model.entry.Attribute;
 import org.apache.directory.shared.ldap.model.entry.Entry;
 import org.apache.directory.shared.ldap.model.entry.Modification;
@@ -79,6 +82,7 @@ import org.apache.directory.shared.ldap.
 import org.apache.directory.shared.ldap.model.filter.ExprNode;
 import org.apache.directory.shared.ldap.model.message.AliasDerefMode;
 import org.apache.directory.shared.ldap.model.message.ResultCodeEnum;
+import org.apache.directory.shared.ldap.model.name.Ava;
 import org.apache.directory.shared.ldap.model.name.Dn;
 import org.apache.directory.shared.ldap.model.name.Rdn;
 import org.apache.directory.shared.ldap.model.schema.AttributeType;
@@ -86,6 +90,7 @@ import org.apache.directory.shared.ldap.
 import org.apache.directory.shared.ldap.model.schema.SchemaManager;
 import org.apache.directory.shared.ldap.model.schema.UsageEnum;
 import org.apache.directory.shared.util.Strings;
+import org.apache.directory.shared.util.exception.MultiException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -103,8 +108,12 @@ public abstract class AbstractBTreeParti
     /** the search engine used to search the database */
     protected SearchEngine<Entry, ID> searchEngine;
     
+    /** The optimizer to use during search operation */
     protected Optimizer optimizer;
     
+    /** Tells if the Optimizer is enabled */
+    protected boolean optimizerEnabled = true;
+
     /** The default cache size is set to 10 000 objects */
     public static final int DEFAULT_CACHE_SIZE = 10000;
 
@@ -189,6 +198,84 @@ public abstract class AbstractBTreeParti
     }
 
 
+    // ------------------------------------------------------------------------
+    // C O N F I G U R A T I O N   M E T H O D S
+    // ------------------------------------------------------------------------
+    /**
+     * Gets the entry cache size for this BTreePartition.
+     *
+     * @return the maximum size of the cache as the number of entries maximum before paging out
+     */
+    public int getCacheSize()
+    {
+        return cacheSize;
+    }
+    
+    
+    /**
+     * Used to specify the entry cache size for a Partition.  Various Partition
+     * implementations may interpret this value in different ways: i.e. total cache
+     * size limit verses the number of entries to cache.
+     *
+     * @param cacheSize the maximum size of the cache in the number of entries
+     */
+    public void setCacheSize( int cacheSize )
+    {
+        this.cacheSize = cacheSize;
+    }
+
+
+    /**
+     * Tells if the Optimizer is enabled or not
+     * @return true if the optimizer is enabled
+     */
+    public boolean isOptimizerEnabled()
+    {
+        return optimizerEnabled;
+    }
+
+
+    /**
+     * Set the optimizer flag
+     * @param optimizerEnabled The flag
+     */
+    public void setOptimizerEnabled( boolean optimizerEnabled )
+    {
+        this.optimizerEnabled = optimizerEnabled;
+    }
+
+    
+    /**
+     * Sets the path in which this Partition stores data. This may be an URL to
+     * a file or directory, or an JDBC URL.
+     *
+     * @param partitionDir the path in which this Partition stores data.
+     */
+    public void setPartitionPath( URI partitionPath )
+    {
+        checkInitialized( "partitionPath" );
+        this.partitionPath = partitionPath;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isSyncOnWrite()
+    {
+        return isSyncOnWrite.get();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setSyncOnWrite( boolean isSyncOnWrite )
+    {
+        checkInitialized( "syncOnWrite" );
+        this.isSyncOnWrite.set( isSyncOnWrite );
+    }
+
+
     /**
      * Sets up the system indices.
      */
@@ -317,6 +404,33 @@ public abstract class AbstractBTreeParti
         userIndices = tmp;
     }
     
+
+    /**
+     * Gets the DefaultSearchEngine used by this ContextPartition to search the
+     * Database.
+     *
+     * @return the search engine
+     */
+    public SearchEngine<Entry, ID> getSearchEngine()
+    {
+        return searchEngine;
+    }
+
+
+    // -----------------------------------------------------------------------
+    // Miscellaneous abstract methods
+    // -----------------------------------------------------------------------
+    /**
+     * {@inheritDoc}}
+     */
+    public abstract ID getDefaultId();
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public abstract ID getRootId();
+    
     
     /**
      * Convert and initialize an index for a specific store implementation.
@@ -329,246 +443,100 @@ public abstract class AbstractBTreeParti
 
 
     /**
-     * builds the Dn of the entry identified by the given id
+     * Gets the path in which this Partition stores data.
      *
-     * @param id the entry's id
-     * @return the normalized Dn of the entry
-     * @throws Exception
+     * @return the path in which this Partition stores data.
      */
-    protected Dn buildEntryDn( ID id ) throws Exception
-    {
-        ID parentId = id;
-        ID rootId = getRootId();
-
-        StringBuilder upName = new StringBuilder();
-        boolean isFirst = true;
-
-        do
-        {
-            ParentIdAndRdn<ID> cur = rdnIdx.reverseLookup( parentId );
-            Rdn[] rdns = cur.getRdns();
-
-            for ( Rdn rdn : rdns )
-            {
-                if ( isFirst )
-                {
-                    isFirst = false;
-                }
-                else
-                {
-                    upName.append( ',' );
-                }
-                
-                upName.append( rdn.getName() );
-            }
-
-            parentId = cur.getParentId();
-        }
-        while ( !parentId.equals( rootId ) );
-
-        Dn dn = new Dn( schemaManager, upName.toString() );
-
-        return dn;
-    }
+    public abstract URI getPartitionPath();
 
 
     // ------------------------------------------------------------------------
-    // C O N F I G U R A T I O N   M E T H O D S
+    // Partition Interface Method Implementations
     // ------------------------------------------------------------------------
     /**
      * {@inheritDoc}
      */
-    public void addIndex( Index<?, Entry, ID> index ) throws Exception
+    protected void doDestroy() throws LdapException, Exception
     {
-        checkInitialized( "addIndex" );
-
-        // Check that the index ID is valid
-        AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() );
+        LOG.debug( "destroy() called on store for {}", this.suffixDn );
 
-        if ( attributeType == null )
+        if ( !initialized )
         {
-            throw new IllegalArgumentException( I18n.err( I18n.ERR_309, index.getAttributeId() ) );
+            return;
         }
 
-        String oid = attributeType.getOid();
+        // don't reset initialized flag
+        initialized = false;
 
-        if ( SYS_INDEX_OIDS.contains( oid ) )
+        MultiException errors = new MultiException( I18n.err( I18n.ERR_577 ) );
+
+        for ( Index<?, Entry, ID> index : userIndices.values() )
         {
-            if ( !systemIndices.containsKey( oid ) )
+            try
             {
-                systemIndices.put( oid, index );
+                index.close();
+                LOG.debug( "Closed {} user index for {} partition.", index.getAttributeId(), suffixDn );
+            }
+            catch ( Throwable t )
+            {
+                LOG.error( I18n.err( I18n.ERR_124 ), t );
+                errors.addThrowable( t );
             }
         }
-        else
+
+        for ( Index<?, Entry, ID> index : systemIndices.values() )
         {
-            if ( !userIndices.containsKey( oid ) )
+            try
             {
-                userIndices.put( oid, index );
+                index.close();
+                LOG.debug( "Closed {} system index for {} partition.", index.getAttributeId(), suffixDn );
+            }
+            catch ( Throwable t )
+            {
+                LOG.error( I18n.err( I18n.ERR_124 ), t );
+                errors.addThrowable( t );
             }
         }
-    }
-
-    
-    /**
-     * Gets the path in which this Partition stores data.
-     *
-     * @return the path in which this Partition stores data.
-     */
-    public abstract URI getPartitionPath();
-
-
-    /**
-     * Sets the path in which this Partition stores data. This may be an URL to
-     * a file or directory, or an JDBC URL.
-     *
-     * @param partitionDir the path in which this Partition stores data.
-     */
-    public void setPartitionPath( URI partitionPath )
-    {
-        checkInitialized( "partitionPath" );
-        this.partitionPath = partitionPath;
-    }
-
 
-    public void setIndexedAttributes( Set<Index<?, Entry, ID>> indexedAttributes )
-    {
-        this.indexedAttributes = indexedAttributes;
-    }
-
-
-    public void addIndexedAttributes( Index<?, Entry, ID>... indexes )
-    {
-        for ( Index<?, Entry, ID> index : indexes )
+        try
         {
-            indexedAttributes.add( index );
+            master.close();
+            LOG.debug( I18n.err( I18n.ERR_125, suffixDn ) );
+        }
+        catch ( Throwable t )
+        {
+            LOG.error( I18n.err( I18n.ERR_126 ), t );
+            errors.addThrowable( t );
         }
-    }
-
-
-    public Set<Index<?, Entry, ID>> getIndexedAttributes()
-    {
-        return indexedAttributes;
-    }
-
-
-    /**
-     * Used to specify the entry cache size for a Partition.  Various Partition
-     * implementations may interpret this value in different ways: i.e. total cache
-     * size limit verses the number of entries to cache.
-     *
-     * @param cacheSize the maximum size of the cache in the number of entries
-     */
-    public void setCacheSize( int cacheSize )
-    {
-        this.cacheSize = cacheSize;
-    }
-
 
-    /**
-     * Gets the entry cache size for this BTreePartition.
-     *
-     * @return the maximum size of the cache as the number of entries maximum before paging out
-     */
-    public int getCacheSize()
-    {
-        return cacheSize;
+        if ( errors.size() > 0 )
+        {
+            throw errors;
+        }
     }
 
     
-    public abstract ID getEntryId( Dn dn ) throws LdapException;
-    
-    public abstract int getChildCount( ID id ) throws LdapException;
-
-    
     /**
      * {@inheritDoc}
      */
-    public ID getParentId( ID childId ) throws Exception
+    protected void doInit() throws Exception
     {
-        ParentIdAndRdn<ID> key = rdnIdx.reverseLookup( childId );
-
-        if ( key == null )
-        {
-            return null;
-        }
-
-        return key.getParentId();
+        setupSystemIndices();
+        setupUserIndices();
     }
 
-
+    //---------------------------------------------------------------------------------------------
+    // The Add operation
+    //---------------------------------------------------------------------------------------------
     /**
-     * Get back an entry knowing its ID
-     *
-     * @param id The Entry ID we want to get back
-     * @return The found Entry, or null if not found
-     * @throws Exception If the lookup failed for any reason (except a not found entry)
+     * {@inheritDoc}
      */
-    public final Entry lookup( ID id ) throws LdapException
+    public void add( AddOperationContext addContext ) throws LdapException
     {
         try
         {
-            Entry entry = master.get( id );
-    
-            if ( entry != null )
-            {
-                // We have to store the DN in this entry
-                Dn dn = buildEntryDn( id );
-                entry.setDn( dn );
-                
-                return new ClonedServerEntry( entry );
-            }
-    
-            return null;
-        }
-        catch ( Exception e )
-        {
-            throw new LdapOperationErrorException( e.getMessage(), e );
-        }
-    }
-    
-
-    public abstract Dn getEntryDn( ID id ) throws Exception;
-
-    // -----------------------------------------------------------------------
-    // E N D   C O N F I G U R A T I O N   M E T H O D S
-    // -----------------------------------------------------------------------
-    /**
-     * {@inheritDoc}
-     */
-    public int count() throws Exception
-    {
-        return master.count();
-    }
-
-
-    // ------------------------------------------------------------------------
-    // Public Accessors - not declared in any interfaces just for this class
-    // ------------------------------------------------------------------------
-
-    /**
-     * Gets the DefaultSearchEngine used by this ContextPartition to search the
-     * Database.
-     *
-     * @return the search engine
-     */
-    public SearchEngine<Entry, ID> getSearchEngine()
-    {
-        return searchEngine;
-    }
-
-
-    // ------------------------------------------------------------------------
-    // Partition Interface Method Implementations
-    // ------------------------------------------------------------------------
-    /**
-     * {@inheritDoc}
-     */
-    public void add( AddOperationContext addContext ) throws LdapException
-    {
-        try
-        {
-            Entry entry = ( ( ClonedServerEntry ) addContext.getEntry() ).getClonedEntry();
-            Dn entryDn = entry.getDn();
+            Entry entry = ( ( ClonedServerEntry ) addContext.getEntry() ).getClonedEntry();
+            Dn entryDn = entry.getDn();
     
             // check if the entry already exists
             if ( getEntryId( entryDn ) != null )
@@ -718,6 +686,9 @@ public abstract class AbstractBTreeParti
     }
 
 
+    //---------------------------------------------------------------------------------------------
+    // The Bind operation
+    //---------------------------------------------------------------------------------------------
     /**
      * {@inheritDoc}
      */
@@ -729,6 +700,9 @@ public abstract class AbstractBTreeParti
     }
 
     
+    //---------------------------------------------------------------------------------------------
+    // The Delete operation
+    //---------------------------------------------------------------------------------------------
     /**
      * {@inheritDoc}
      */
@@ -761,9 +735,83 @@ public abstract class AbstractBTreeParti
      * @param id The id of the entry to delete
      * @throws Exception If the deletion failed
      */
-    public abstract void delete( ID id ) throws LdapException;
+    public void delete( ID id ) throws LdapException
+    {
+        try
+        {
+            // First get the entry
+            Entry entry = master.get( id );
+            
+            if ( entry == null )
+            {
+                // Not allowed
+                throw new LdapNoSuchObjectException( "Cannot find an entry for ID " + id );
+            }
+
+            Attribute objectClass = entry.get( OBJECT_CLASS_AT );
+
+            if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
+            {
+                dropAliasIndices( id );
+            }
+
+            // Update the ObjectClass index
+            for ( Value<?> value : objectClass )
+            {
+                objectClassIdx.drop( value.getString(), id );
+            }
+
+            // Update the rdn, oneLevel, subLevel, entryCsn and entryUuid indexes
+            rdnIdx.drop( id );
+            oneLevelIdx.drop( id );
+            subLevelIdx.drop( id );
+            entryCsnIdx.drop( id );
+            entryUuidIdx.drop( id );
+
+            // Update the user indexes
+            for ( Attribute attribute : entry )
+            {
+                AttributeType attributeType = attribute.getAttributeType();
+                String attributeOid = attributeType.getOid();
+
+                if ( hasUserIndexOn( attributeType ) )
+                {
+                    Index<?, Entry, ID> index = getUserIndex( attributeType );
+
+                    // here lookup by attributeId is ok since we got attributeId from
+                    // the entry via the enumeration - it's in there as is for sure
+                    for ( Value<?> value : attribute )
+                    {
+                        ( ( Index ) index ).drop( value.getValue(), id );
+                    }
+
+                    presenceIdx.drop( attributeOid, id );
+                }
+            }
+
+            master.remove( id );
+            
+            // if this is a context entry reset the master table counter
+            if ( id.equals( getDefaultId() ) )
+            {
+                master.resetCounter();
+            }
+
+            if ( isSyncOnWrite.get() )
+            {
+                sync();
+            }
+        }
+        catch ( Exception e )
+        {
+            throw new LdapOperationErrorException( e.getMessage(), e );
+        }
+    }
 
     
+    //---------------------------------------------------------------------------------------------
+    // The List operation
+    //---------------------------------------------------------------------------------------------
     /**
      * {@inheritDoc}
      */
@@ -774,13 +822,35 @@ public abstract class AbstractBTreeParti
                 list( getEntryId( listContext.getDn() ) ) ), listContext );
     }
 
-
+    
     /**
      * {@inheritDoc}
      */
-    public abstract IndexCursor<ID, Entry, ID> list( ID id ) throws LdapException;
+    public final IndexCursor<ID, Entry, ID> list( ID id ) throws LdapException
+    {
+        try
+        {
+            // We use the OneLevel index to get all the entries from a starting point
+            // and below
+            IndexCursor<ID, Entry, ID> cursor = oneLevelIdx.forwardCursor( id );
+            cursor.beforeValue( id, null );
+            
+            return cursor;
+         }
+        catch ( Exception e )
+        {
+            throw new LdapOperationErrorException( e.getMessage(), e );
+        }
+    }
 
-    
+
+
+    //---------------------------------------------------------------------------------------------
+    // The Search operation
+    //---------------------------------------------------------------------------------------------
+    /**
+     * {@inheritDoc}
+     */
     public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException
     {
         try
@@ -807,6 +877,9 @@ public abstract class AbstractBTreeParti
     }
 
 
+    //---------------------------------------------------------------------------------------------
+    // The Lookup operation
+    //---------------------------------------------------------------------------------------------
     /**
      * {@inheritDoc}
      */
@@ -890,46 +963,992 @@ public abstract class AbstractBTreeParti
             }
         }
 
-        return entry;
+        return entry;
+    }
+
+
+    /**
+     * Get back an entry knowing its ID
+     *
+     * @param id The Entry ID we want to get back
+     * @return The found Entry, or null if not found
+     * @throws Exception If the lookup failed for any reason (except a not found entry)
+     */
+    public final Entry lookup( ID id ) throws LdapException
+    {
+        try
+        {
+            Entry entry = master.get( id );
+    
+            if ( entry != null )
+            {
+                // We have to store the DN in this entry
+                Dn dn = buildEntryDn( id );
+                entry.setDn( dn );
+                
+                return new ClonedServerEntry( entry );
+            }
+    
+            return null;
+        }
+        catch ( Exception e )
+        {
+            throw new LdapOperationErrorException( e.getMessage(), e );
+        }
+    }
+
+
+    //---------------------------------------------------------------------------------------------
+    // The Modify operation
+    //---------------------------------------------------------------------------------------------
+    /**
+     * {@inheritDoc}
+     */
+    public void modify( ModifyOperationContext modifyContext ) throws LdapException
+    {
+        try
+        {
+            Entry modifiedEntry = modify( modifyContext.getDn(), modifyContext.getModItems().toArray( new Modification[]{}) );
+            modifyContext.setAlteredEntry( modifiedEntry );
+        }
+        catch ( Exception e )
+        {
+            throw new LdapOperationErrorException( e.getMessage(), e );
+        }
+    }
+    
+    
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized final Entry modify( Dn dn, Modification... mods ) throws Exception
+    {
+        ID id = getEntryId( dn );
+        Entry entry = master.get( id );
+        
+        for ( Modification mod : mods )
+        {
+            Attribute attrMods = mod.getAttribute();
+
+            switch ( mod.getOperation() )
+            {
+                case ADD_ATTRIBUTE:
+                    modifyAdd( id, entry, attrMods );
+                    break;
+
+                case REMOVE_ATTRIBUTE:
+                    modifyRemove( id, entry, attrMods );
+                    break;
+
+                case REPLACE_ATTRIBUTE:
+                    modifyReplace( id, entry, attrMods );
+                    break;
+
+                default:
+                    throw new LdapException( I18n.err( I18n.ERR_221 ) );
+            }
+        }
+
+        updateCsnIndex( entry, id );
+        master.put( id, entry );
+
+        if ( isSyncOnWrite.get() )
+        {
+            sync();
+        }
+
+        return entry;
+    }
+    
+    
+    /**
+     * Adds a set of attribute values while affecting the appropriate userIndices.
+     * The entry is not persisted: it is only changed in anticipation for a put
+     * into the master table.
+     *
+     * @param id the primary key of the entry
+     * @param entry the entry to alter
+     * @param mods the attribute and values to add
+     * @throws Exception if index alteration or attribute addition fails
+     */
+    @SuppressWarnings("unchecked")
+    private void modifyAdd( ID id, Entry entry, Attribute mods ) throws Exception
+    {
+        if ( entry instanceof ClonedServerEntry )
+        {
+            throw new Exception( I18n.err( I18n.ERR_215 ) );
+        }
+
+        String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
+        AttributeType attributeType = mods.getAttributeType();
+
+        // Special case for the ObjectClass index
+        if ( modsOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
+        {
+            for ( Value<?> value : mods )
+            {
+                objectClassIdx.add( value.getString(), id );
+            }
+        }
+        else if ( hasUserIndexOn( attributeType ) )
+        {
+            Index<?, Entry, ID> index = getUserIndex( attributeType );
+
+            for ( Value<?> value : mods )
+            {
+                ( ( Index ) index ).add( value.getValue(), id );
+            }
+
+            // If the attr didn't exist for this id add it to presence index
+            if ( !presenceIdx.forward( modsOid, id ) )
+            {
+                presenceIdx.add( modsOid, id );
+            }
+        }
+
+        // add all the values in mods to the same attribute in the entry
+
+        for ( Value<?> value : mods )
+        {
+            entry.add( mods.getAttributeType(), value );
+        }
+
+        if ( modsOid.equals( SchemaConstants.ALIASED_OBJECT_NAME_AT_OID ) )
+        {
+            Dn ndn = getEntryDn( id );
+            addAliasIndices( id, ndn, mods.getString() );
+        }
+    }
+
+
+    /**
+     * Completely replaces the existing set of values for an attribute with the
+     * modified values supplied affecting the appropriate userIndices.  The entry
+     * is not persisted: it is only changed in anticipation for a put into the
+     * master table.
+     *
+     * @param id the primary key of the entry
+     * @param entry the entry to alter
+     * @param mods the replacement attribute and values
+     * @throws Exception if index alteration or attribute modification
+     * fails.
+     */
+    @SuppressWarnings("unchecked")
+    private void modifyReplace( ID id, Entry entry, Attribute mods ) throws Exception
+    {
+        if ( entry instanceof ClonedServerEntry )
+        {
+            throw new Exception( I18n.err( I18n.ERR_215 ) );
+        }
+
+        String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
+        AttributeType attributeType = mods.getAttributeType();
+
+        // Special case for the ObjectClass index
+        if ( attributeType.equals( OBJECT_CLASS_AT ) )
+        {
+            // if the id exists in the index drop all existing attribute
+            // value index entries and add new ones
+            if ( objectClassIdx.reverse( id ) )
+            {
+                objectClassIdx.drop( id );
+            }
+
+            for ( Value<?> value : mods )
+            {
+                objectClassIdx.add( value.getString(), id );
+            }
+        }
+        else if ( hasUserIndexOn( attributeType ) )
+        {
+            Index<?, Entry, ID> index = getUserIndex( attributeType );
+
+            // if the id exists in the index drop all existing attribute
+            // value index entries and add new ones
+            if ( index.reverse( id ) )
+            {
+                ( ( Index<?, Entry, ID> ) index ).drop( id );
+            }
+
+            for ( Value<?> value : mods )
+            {
+                ( ( Index<Object, Entry, ID> ) index ).add( value.getValue(), id );
+            }
+
+            /*
+             * If no attribute values exist for this entryId in the index then
+             * we remove the presence index entry for the removed attribute.
+             */
+            if ( null == index.reverseLookup( id ) )
+            {
+                presenceIdx.drop( modsOid, id );
+            }
+        }
+
+        String aliasAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName(
+            SchemaConstants.ALIASED_OBJECT_NAME_AT );
+
+        if ( mods.getAttributeType().equals( ALIASED_OBJECT_NAME_AT ) )
+        {
+            dropAliasIndices( id );
+        }
+
+        // replaces old attributes with new modified ones if they exist
+        if ( mods.size() > 0 )
+        {
+            entry.put( mods );
+        }
+        else
+        // removes old attributes if new replacements do not exist
+        {
+            entry.remove( mods );
+        }
+
+        if ( modsOid.equals( aliasAttributeOid ) && mods.size() > 0 )
+        {
+            Dn entryDn = getEntryDn( id );
+            addAliasIndices( id, entryDn, mods.getString() );
+        }
+    }
+
+
+    /**
+     * Completely removes the set of values for an attribute having the values
+     * supplied while affecting the appropriate userIndices.  The entry is not
+     * persisted: it is only changed in anticipation for a put into the master
+     * table.  Note that an empty attribute w/o values will remove all the
+     * values within the entry where as an attribute w/ values will remove those
+     * attribute values it contains.
+     *
+     * @param id the primary key of the entry
+     * @param entry the entry to alter
+     * @param mods the attribute and its values to delete
+     * @throws Exception if index alteration or attribute modification fails.
+     */
+    @SuppressWarnings("unchecked")
+    private void modifyRemove( ID id, Entry entry, Attribute mods ) throws Exception
+    {
+        if ( entry instanceof ClonedServerEntry )
+        {
+            throw new Exception( I18n.err( I18n.ERR_215 ) );
+        }
+
+        String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
+        AttributeType attributeType = mods.getAttributeType();
+
+        // Special case for the ObjectClass index
+        if ( attributeType.equals( OBJECT_CLASS_AT ) )
+        {
+            /*
+             * If there are no attribute values in the modifications then this
+             * implies the complete removal of the attribute from the index. Else
+             * we remove individual tuples from the index.
+             */
+            if ( mods.size() == 0 )
+            {
+                objectClassIdx.drop( id );
+            }
+            else
+            {
+                for ( Value<?> value : mods )
+                {
+                    objectClassIdx.drop( value.getString(), id );
+                }
+            }
+        }
+        else if ( hasUserIndexOn( attributeType ) )
+        {
+            Index<?, Entry, ID> index = getUserIndex( attributeType );
+
+            /*
+             * If there are no attribute values in the modifications then this
+             * implies the complete removal of the attribute from the index. Else
+             * we remove individual tuples from the index.
+             */
+            if ( mods.size() == 0 )
+            {
+                ( ( Index ) index ).drop( id );
+            }
+            else
+            {
+                for ( Value<?> value : mods )
+                {
+                    ( ( Index ) index ).drop( value.getValue(), id );
+                }
+            }
+
+            /*
+             * If no attribute values exist for this entryId in the index then
+             * we remove the presence index entry for the removed attribute.
+             */
+            if ( null == index.reverseLookup( id ) )
+            {
+                presenceIdx.drop( modsOid, id );
+            }
+        }
+
+        AttributeType attrType = schemaManager.lookupAttributeTypeRegistry( modsOid );
+
+        /*
+         * If there are no attribute values in the modifications then this
+         * implies the complete removal of the attribute from the entry. Else
+         * we remove individual attribute values from the entry in mods one
+         * at a time.
+         */
+        if ( mods.size() == 0 )
+        {
+            entry.removeAttributes( mods.getAttributeType() );
+        }
+        else
+        {
+            Attribute entryAttr = entry.get( mods.getAttributeType() );
+
+            for ( Value<?> value : mods )
+            {
+                entryAttr.remove( value );
+            }
+
+            // if nothing is left just remove empty attribute
+            if ( entryAttr.size() == 0 )
+            {
+                entry.removeAttributes( entryAttr.getId() );
+            }
+        }
+
+        // Aliases->single valued comp/partial attr removal is not relevant here
+        if ( mods.getAttributeType().equals( ALIASED_OBJECT_NAME_AT ) )
+        {
+            dropAliasIndices( id );
+        }
+    }
+
+
+    //---------------------------------------------------------------------------------------------
+    // The Move operation
+    //---------------------------------------------------------------------------------------------
+    /**
+     * {@inheritDoc}
+     */
+    public void move( MoveOperationContext moveContext ) throws LdapException
+    {
+        if ( moveContext.getNewSuperior().isDescendantOf( moveContext.getDn() ) )
+        {
+            throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
+                "cannot place an entry below itself" );
+        }
+
+        try
+        {
+            Dn oldDn = moveContext.getDn();
+            Dn newSuperior = moveContext.getNewSuperior();
+            Dn newDn = moveContext.getNewDn();
+            Entry modifiedEntry = moveContext.getModifiedEntry();
+            
+            move( oldDn, newSuperior, newDn, modifiedEntry );
+        }
+        catch ( Exception e )
+        {
+            throw new LdapOperationErrorException( e.getMessage(), e );
+        }
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized final void move( Dn oldDn, Dn newSuperiorDn, Dn newDn, Entry modifiedEntry ) throws Exception
+    {
+        // Check that the parent Dn exists
+        ID newParentId = getEntryId( newSuperiorDn );
+
+        if ( newParentId == null )
+        {
+            // This is not allowed : the parent must exist
+            LdapEntryAlreadyExistsException ne = new LdapEntryAlreadyExistsException(
+                I18n.err( I18n.ERR_256_NO_SUCH_OBJECT, newSuperiorDn.getName() ) );
+            throw ne;
+        }
+
+        // Now check that the new entry does not exist
+        ID newId = getEntryId( newDn );
+
+        if ( newId != null )
+        {
+            // This is not allowed : we should not be able to move an entry
+            // to an existing position
+            LdapEntryAlreadyExistsException ne = new LdapEntryAlreadyExistsException(
+                I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, newSuperiorDn.getName() ) );
+            throw ne;
+        }
+
+        // Get the entry and the old parent IDs
+        ID entryId = getEntryId( oldDn );
+        ID oldParentId = getParentId( entryId );
+
+        /*
+         * All aliases including and below oldChildDn, will be affected by
+         * the move operation with respect to one and subtree userIndices since
+         * their relationship to ancestors above oldChildDn will be
+         * destroyed.  For each alias below and including oldChildDn we will
+         * drop the index tuples mapping ancestor ids above oldChildDn to the
+         * respective target ids of the aliases.
+         */
+        dropMovedAliasIndices( oldDn );
+
+        /*
+         * Drop the old parent child relationship and add the new one
+         * Set the new parent id for the child replacing the old parent id
+         */
+        oneLevelIdx.drop( oldParentId, entryId );
+        oneLevelIdx.add( newParentId, entryId );
+
+        updateSubLevelIndex( entryId, oldParentId, newParentId );
+
+        // Update the Rdn index
+        rdnIdx.drop( entryId );
+        ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( newParentId, oldDn.getRdn() );
+        rdnIdx.add( key, entryId );
+
+
+        /*
+         * Read Alias Index Tuples
+         *
+         * If this is a name change due to a move operation then the one and
+         * subtree userIndices for aliases were purged before the aliases were
+         * moved.  Now we must add them for each alias entry we have moved.
+         *
+         * aliasTarget is used as a marker to tell us if we're moving an
+         * alias.  If it is null then the moved entry is not an alias.
+         */
+        String aliasTarget = aliasIdx.reverseLookup( entryId );
+
+        if ( null != aliasTarget )
+        {
+            addAliasIndices( entryId, buildEntryDn( entryId ), aliasTarget );
+        }
+
+        // the below case arises only when the move( Dn oldDn, Dn newSuperiorDn, Dn newDn  ) is called
+        // directly using the Store API, in this case the value of modified entry will be null
+        // we need to lookup the entry to update the parent ID
+        if ( modifiedEntry == null )
+        {
+            modifiedEntry = lookup( entryId );
+        }
+        
+        // Update the master table with the modified entry
+        modifiedEntry.put( SchemaConstants.ENTRY_PARENT_ID_AT, newParentId.toString() );
+        master.put( entryId, modifiedEntry );
+
+        if ( isSyncOnWrite.get() )
+        {
+            sync();
+        }
+    }
+
+
+    //---------------------------------------------------------------------------------------------
+    // The MoveAndRename operation
+    //---------------------------------------------------------------------------------------------
+    /**
+     * {@inheritDoc}
+     */
+    public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
+    {
+        if ( moveAndRenameContext.getNewSuperiorDn().isDescendantOf( moveAndRenameContext.getDn() ) )
+        {
+            throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
+                "cannot place an entry below itself" );
+        }
+
+        try
+        {
+            Dn oldDn = moveAndRenameContext.getDn();
+            Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn();
+            Rdn newRdn = moveAndRenameContext.getNewRdn();
+            boolean deleteOldRdn = moveAndRenameContext.getDeleteOldRdn();
+            Entry modifiedEntry = moveAndRenameContext.getModifiedEntry();
+            
+            moveAndRename( oldDn, newSuperiorDn, newRdn, modifiedEntry, deleteOldRdn );
+        }
+        catch ( LdapException le )
+        {
+            // In case we get an LdapException, just rethrow it as is to 
+            // avoid having it lost
+            throw le;
+        }
+        catch ( Exception e )
+        {
+            throw new LdapOperationErrorException( e.getMessage(), e );
+        }
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized final void moveAndRename( Dn oldDn, Dn newSuperiorDn, Rdn newRdn, Entry modifiedEntry, boolean deleteOldRdn ) throws Exception
+    {
+        // Check that the old entry exists
+        ID oldId = getEntryId( oldDn );
+
+        if ( oldId == null )
+        {
+            // This is not allowed : the old entry must exist
+            LdapNoSuchObjectException nse = new LdapNoSuchObjectException(
+                I18n.err( I18n.ERR_256_NO_SUCH_OBJECT, oldDn ) );
+            throw nse;
+        }
+
+        // Check that the new superior exist
+        ID newSuperiorId = getEntryId( newSuperiorDn );
+
+        if ( newSuperiorId == null )
+        {
+            // This is not allowed : the new superior must exist
+            LdapNoSuchObjectException nse = new LdapNoSuchObjectException(
+                I18n.err( I18n.ERR_256_NO_SUCH_OBJECT, newSuperiorDn ) );
+            throw nse;
+        }
+
+        Dn newDn = newSuperiorDn.add( newRdn );
+
+        // Now check that the new entry does not exist
+        ID newId = getEntryId( newDn );
+
+        if ( newId != null )
+        {
+            // This is not allowed : we should not be able to move an entry
+            // to an existing position
+            LdapEntryAlreadyExistsException ne = new LdapEntryAlreadyExistsException(
+                I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, newSuperiorDn.getName() ) );
+            throw ne;
+        }
+
+        rename( oldDn, newRdn, deleteOldRdn, modifiedEntry );
+        moveAndRename( oldDn, oldId, newSuperiorDn, newRdn, modifiedEntry );
+
+        if ( isSyncOnWrite.get() )
+        {
+            sync();
+        }
+    }
+
+
+    /**
+     * Moves an entry under a new parent.  The operation causes a shift in the
+     * parent child relationships between the old parent, new parent and the
+     * child moved.  All other descendant entries under the child never change
+     * their direct parent child relationships.  Hence after the parent child
+     * relationship changes are broken at the old parent and set at the new
+     * parent a modifyDn operation is conducted to handle name changes
+     * propagating down through the moved child and its descendants.
+     *
+     * @param oldDn the normalized dn of the child to be moved
+     * @param childId the id of the child being moved
+     * @param newRdn the normalized dn of the new parent for the child
+     * @param modifiedEntry the modified entry
+     * @throws Exception if something goes wrong
+     */
+    private void moveAndRename( Dn oldDn, ID childId, Dn newSuperior, Rdn newRdn, Entry modifiedEntry ) throws Exception
+    {
+        // Get the child and the new parent to be entries and Ids
+        ID newParentId = getEntryId( newSuperior );
+        ID oldParentId = getParentId( childId );
+
+        /*
+         * All aliases including and below oldChildDn, will be affected by
+         * the move operation with respect to one and subtree userIndices since
+         * their relationship to ancestors above oldChildDn will be
+         * destroyed.  For each alias below and including oldChildDn we will
+         * drop the index tuples mapping ancestor ids above oldChildDn to the
+         * respective target ids of the aliases.
+         */
+        dropMovedAliasIndices( oldDn );
+
+        /*
+         * Drop the old parent child relationship and add the new one
+         * Set the new parent id for the child replacing the old parent id
+         */
+        oneLevelIdx.drop( oldParentId, childId );
+        oneLevelIdx.add( newParentId, childId );
+
+        updateSubLevelIndex( childId, oldParentId, newParentId );
+
+        /*
+         * Update the Rdn index
+         */
+        rdnIdx.drop( childId );
+        ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( newParentId, newRdn );
+        rdnIdx.add( key, childId );
+
+        /*
+         * Read Alias Index Tuples
+         *
+         * If this is a name change due to a move operation then the one and
+         * subtree userIndices for aliases were purged before the aliases were
+         * moved.  Now we must add them for each alias entry we have moved.
+         *
+         * aliasTarget is used as a marker to tell us if we're moving an
+         * alias.  If it is null then the moved entry is not an alias.
+         */
+        String aliasTarget = aliasIdx.reverseLookup( childId );
+
+        if ( null != aliasTarget )
+        {
+            addAliasIndices( childId, buildEntryDn( childId ), aliasTarget );
+        }
+
+        // Update the master table with the modified entry
+        // Warning : this test is an hack. As we may call the Store API directly
+        // we may not have a modified entry to update. For instance, if the ModifierName
+        // or ModifyTimeStamp AT are not updated, there is no reason we want to update the
+        // master table.
+        if ( modifiedEntry != null )
+        {
+            modifiedEntry.put( SchemaConstants.ENTRY_PARENT_ID_AT, newParentId.toString() );
+            master.put( childId, modifiedEntry );
+        }
+    }
+
+
+    //---------------------------------------------------------------------------------------------
+    // The Rename operation
+    //---------------------------------------------------------------------------------------------
+    /**
+     * {@inheritDoc}
+     */
+    public void rename( RenameOperationContext renameContext ) throws LdapException
+    {
+        try
+        {
+            Dn oldDn = renameContext.getDn();
+            Rdn newRdn = renameContext.getNewRdn();
+            boolean deleteOldRdn = renameContext.getDeleteOldRdn();
+
+            if ( renameContext.getEntry() != null )
+            {
+                Entry modifiedEntry = renameContext.getModifiedEntry();
+                rename( oldDn, newRdn, deleteOldRdn, modifiedEntry );
+            }
+            else
+            {
+                rename( oldDn, newRdn, deleteOldRdn, null );
+            }
+        }
+        catch ( Exception e )
+        {
+            throw new LdapOperationErrorException( e.getMessage(), e );
+        }
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public synchronized final void rename( Dn dn, Rdn newRdn, boolean deleteOldRdn, Entry entry ) throws Exception
+    {
+        ID id = getEntryId( dn );
+
+        if ( entry == null )
+        {
+            entry = lookup( id );
+        }
+
+        Dn updn = entry.getDn();
+
+        newRdn.apply( schemaManager );
+
+        /*
+         * H A N D L E   N E W   R D N
+         * ====================================================================
+         * Add the new Rdn attribute to the entry.  If an index exists on the
+         * new Rdn attribute we add the index for this attribute value pair.
+         * Also we make sure that the presence index shows the existence of the
+         * new Rdn attribute within this entry.
+         */
+
+        for ( Ava newAtav : newRdn )
+        {
+            String newNormType = newAtav.getNormType();
+            Object newNormValue = newAtav.getNormValue().getValue();
+
+            AttributeType newRdnAttrType = schemaManager.lookupAttributeTypeRegistry( newNormType );
+
+            entry.add( newRdnAttrType, newAtav.getNormValue() );
+
+            if ( hasUserIndexOn( newRdnAttrType ) )
+            {
+                Index<?, Entry, ID> index = getUserIndex( newRdnAttrType );
+                ( ( Index ) index ).add( newNormValue, id );
+
+                // Make sure the altered entry shows the existence of the new attrib
+                if ( !presenceIdx.forward( newNormType, id ) )
+                {
+                    presenceIdx.add( newNormType, id );
+                }
+            }
+        }
+
+        /*
+         * H A N D L E   O L D   R D N
+         * ====================================================================
+         * If the old Rdn is to be removed we need to get the attribute and
+         * value for it.  Keep in mind the old Rdn need not be based on the
+         * same attr as the new one.  We remove the Rdn value from the entry
+         * and remove the value/id tuple from the index on the old Rdn attr
+         * if any.  We also test if the delete of the old Rdn index tuple
+         * removed all the attribute values of the old Rdn using a reverse
+         * lookup.  If so that means we blew away the last value of the old
+         * Rdn attribute.  In this case we need to remove the attrName/id
+         * tuple from the presence index.
+         *
+         * We only remove an ATAV of the old Rdn if it is not included in the
+         * new Rdn.
+         */
+
+        if ( deleteOldRdn )
+        {
+            Rdn oldRdn = updn.getRdn();
+
+            for ( Ava oldAtav : oldRdn )
+            {
+                // check if the new ATAV is part of the old Rdn
+                // if that is the case we do not remove the ATAV
+                boolean mustRemove = true;
+
+                for ( Ava newAtav : newRdn )
+                {
+                    if ( oldAtav.equals( newAtav ) )
+                    {
+                        mustRemove = false;
+                        break;
+                    }
+                }
+
+                if ( mustRemove )
+                {
+                    String oldNormType = oldAtav.getNormType();
+                    String oldNormValue = oldAtav.getNormValue().getString();
+                    AttributeType oldRdnAttrType = schemaManager.lookupAttributeTypeRegistry( oldNormType );
+                    entry.remove( oldRdnAttrType, oldNormValue );
+
+                    if ( hasUserIndexOn( oldRdnAttrType ) )
+                    {
+                        Index<?, Entry, ID> index = getUserIndex( oldRdnAttrType );
+                        ( ( Index ) index ).drop( oldNormValue, id );
+
+                        /*
+                         * If there is no value for id in this index due to our
+                         * drop above we remove the oldRdnAttr from the presence idx
+                         */
+                        if ( null == index.reverseLookup( id ) )
+                        {
+                            presenceIdx.drop( oldNormType, id );
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*
+         * H A N D L E   D N   C H A N G E
+         * ====================================================================
+         * We only need to update the Rdn index.
+         * No need to calculate the new Dn.
+         */
+
+        ID parentId = getParentId( id );
+        rdnIdx.drop( id );
+        ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( parentId, newRdn );
+        rdnIdx.add( key, id );
+
+        master.put( id, entry );
+
+        if ( isSyncOnWrite.get() )
+        {
+            sync();
+        }
+    }
+
+
+    //---------------------------------------------------------------------------------------------
+    // The Unbind operation
+    //---------------------------------------------------------------------------------------------
+    /**
+     * {@inheritDoc}
+     */
+    public final void unbind( UnbindOperationContext unbindContext ) throws LdapException
+    {
+        // does nothing
+    }
+
+
+    /**
+     * This method calls {@link Partition#lookup(LookupOperationContext)} and return <tt>true</tt>
+     * if it returns an entry by default.  Please override this method if
+     * there is more effective way for your implementation.
+     */
+    public final boolean hasEntry( EntryOperationContext entryContext ) throws LdapException
+    {
+        try
+        {
+            ID id = getEntryId( entryContext.getDn() );
+
+            Entry entry = lookup( id );
+            
+            return entry != null; 
+        }
+        catch ( LdapException e )
+        {
+            return false;
+        }
+    }
+
+
+    //---------------------------------------------------------------------------------------------
+    // Helper methods
+    //---------------------------------------------------------------------------------------------
+    /**
+     * updates the CSN index
+     *
+     * @param entry the entry having entryCSN attribute
+     * @param id ID of the entry
+     * @throws Exception
+     */
+    private void updateCsnIndex( Entry entry, ID id ) throws Exception
+    {
+        entryCsnIdx.drop( id );
+        entryCsnIdx.add( entry.get( SchemaConstants.ENTRY_CSN_AT ).getString(), id );
+    }
+
+
+    /**
+     * Updates the SubLevel Index as part of a move operation.
+     *
+     * @param entryId child id to be moved
+     * @param oldParentId old parent's id
+     * @param newParentId new parent's id
+     * @throws Exception
+     */
+    private void updateSubLevelIndex( ID entryId, ID oldParentId, ID newParentId ) throws Exception
+    {
+        ID tempId = oldParentId;
+        List<ID> parentIds = new ArrayList<ID>();
+
+        // find all the parents of the oldParentId
+        while ( ( tempId != null ) && !tempId.equals( getRootId() ) && !tempId.equals( getSuffixId() ) )
+        {
+            parentIds.add( tempId );
+            tempId = getParentId( tempId );
+        }
+
+        // find all the children of the childId
+        Cursor<IndexEntry<ID, Entry, ID>> cursor = subLevelIdx.forwardCursor( entryId );
+
+        List<ID> childIds = new ArrayList<ID>();
+        childIds.add( entryId );
+
+        while ( cursor.next() )
+        {
+            childIds.add( cursor.get().getId() );
+        }
+
+        // detach the childId and all its children from oldParentId and all it parents excluding the root
+        for ( ID pid : parentIds )
+        {
+            for ( ID cid : childIds )
+            {
+                subLevelIdx.drop( pid, cid );
+            }
+        }
+
+        parentIds.clear();
+        tempId = newParentId;
+
+        // find all the parents of the newParentId
+        while ( ( tempId != null)  && !tempId.equals( getRootId() ) && !tempId.equals( getSuffixId() ) )
+        {
+            parentIds.add( tempId );
+            tempId = getParentId( tempId );
+        }
+
+        // attach the childId and all its children to newParentId and all it parents excluding the root
+        for ( ID id : parentIds )
+        {
+            for ( ID cid : childIds )
+            {
+                subLevelIdx.add( id, cid );
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Index and master table Operations
+    // ------------------------------------------------------------------------
+    /**
+     * builds the Dn of the entry identified by the given id
+     *
+     * @param id the entry's id
+     * @return the normalized Dn of the entry
+     * @throws Exception
+     */
+    protected Dn buildEntryDn( ID id ) throws Exception
+    {
+        ID parentId = id;
+        ID rootId = getRootId();
+
+        StringBuilder upName = new StringBuilder();
+        boolean isFirst = true;
+
+        do
+        {
+            ParentIdAndRdn<ID> cur = rdnIdx.reverseLookup( parentId );
+            Rdn[] rdns = cur.getRdns();
+
+            for ( Rdn rdn : rdns )
+            {
+                if ( isFirst )
+                {
+                    isFirst = false;
+                }
+                else
+                {
+                    upName.append( ',' );
+                }
+                
+                upName.append( rdn.getName() );
+            }
+
+            parentId = cur.getParentId();
+        }
+        while ( !parentId.equals( rootId ) );
+
+        Dn dn = new Dn( schemaManager, upName.toString() );
+
+        return dn;
     }
 
 
     /**
      * {@inheritDoc}
      */
-    public void modify( ModifyOperationContext modifyContext ) throws LdapException
+    public int count() throws Exception
     {
-        try
-        {
-            Entry modifiedEntry = modify( modifyContext.getDn(), modifyContext.getModItems().toArray( new Modification[]{}) );
-            modifyContext.setAlteredEntry( modifiedEntry );
-        }
-        catch ( Exception e )
-        {
-            throw new LdapOperationErrorException( e.getMessage(), e );
-        }
+        return master.count();
     }
-    
-    
+
+
     /**
      * {@inheritDoc}
      */
-    public void move( MoveOperationContext moveContext ) throws LdapException
+    public final int getChildCount( ID id ) throws LdapException
     {
-        if ( moveContext.getNewSuperior().isDescendantOf( moveContext.getDn() ) )
-        {
-            throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
-                "cannot place an entry below itself" );
-        }
-
         try
         {
-            Dn oldDn = moveContext.getDn();
-            Dn newSuperior = moveContext.getNewSuperior();
-            Dn newDn = moveContext.getNewDn();
-            Entry modifiedEntry = moveContext.getModifiedEntry();
-            
-            move( oldDn, newSuperior, newDn, modifiedEntry );
+            return oneLevelIdx.count( id );
         }
         catch ( Exception e )
         {
@@ -941,61 +1960,46 @@ public abstract class AbstractBTreeParti
     /**
      * {@inheritDoc}
      */
-    public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
+    public final Dn getEntryDn( ID id ) throws Exception
     {
-        if ( moveAndRenameContext.getNewSuperiorDn().isDescendantOf( moveAndRenameContext.getDn() ) )
-        {
-            throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
-                "cannot place an entry below itself" );
-        }
-
-        try
-        {
-            Dn oldDn = moveAndRenameContext.getDn();
-            Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn();
-            Rdn newRdn = moveAndRenameContext.getNewRdn();
-            boolean deleteOldRdn = moveAndRenameContext.getDeleteOldRdn();
-            Entry modifiedEntry = moveAndRenameContext.getModifiedEntry();
-            
-            moveAndRename( oldDn, newSuperiorDn, newRdn, modifiedEntry, deleteOldRdn );
-        }
-        catch ( LdapException le )
-        {
-            // In case we get an LdapException, just rethrow it as is to 
-            // avoid having it lost
-            throw le;
-        }
-        catch ( Exception e )
-        {
-            throw new LdapOperationErrorException( e.getMessage(), e );
-        }
+        return buildEntryDn( id );
     }
-
-
+    
+    
     /**
      * {@inheritDoc}
      */
-    public void rename( RenameOperationContext renameContext ) throws LdapException
+    public final ID getEntryId( Dn dn ) throws LdapException
     {
         try
         {
-            Dn oldDn = renameContext.getDn();
-            Rdn newRdn = renameContext.getNewRdn();
-            boolean deleteOldRdn = renameContext.getDeleteOldRdn();
-
-            if ( renameContext.getEntry() != null )
+            if ( Dn.isNullOrEmpty( dn ) )
             {
-                Entry modifiedEntry = renameContext.getModifiedEntry();
-                rename( oldDn, newRdn, deleteOldRdn, modifiedEntry );
+                return getRootId();
             }
-            else
+
+            ParentIdAndRdn<ID> suffixKey = new ParentIdAndRdn<ID>( getRootId(), suffixDn.getRdns() );
+
+            // Check into the Rdn index, starting with the partition Suffix
+            ID currentId = rdnIdx.forwardLookup( suffixKey );
+
+            for ( int i = dn.size() - suffixDn.size(); i > 0; i-- )
             {
-                rename( oldDn, newRdn, deleteOldRdn, null );
+                Rdn rdn = dn.getRdn( i - 1 );
+                ParentIdAndRdn<ID> currentRdn = new ParentIdAndRdn<ID>( currentId, rdn );
+                currentId = rdnIdx.forwardLookup( currentRdn );
+
+                if ( currentId == null )
+                {
+                    break;
+                }
             }
+
+            return currentId;
         }
         catch ( Exception e )
         {
-            throw new LdapOperationErrorException( e.getMessage(), e );
+            throw new LdapException( e.getMessage(), e );
         }
     }
 
@@ -1003,37 +2007,19 @@ public abstract class AbstractBTreeParti
     /**
      * {@inheritDoc}
      */
-    public final void unbind( UnbindOperationContext unbindContext ) throws LdapException
-    {
-        // does nothing
-    }
-
-
-    /**
-     * This method calls {@link Partition#lookup(LookupOperationContext)} and return <tt>true</tt>
-     * if it returns an entry by default.  Please override this method if
-     * there is more effective way for your implementation.
-     */
-    public final boolean hasEntry( EntryOperationContext entryContext ) throws LdapException
+    public ID getParentId( ID childId ) throws Exception
     {
-        try
-        {
-            ID id = getEntryId( entryContext.getDn() );
+        ParentIdAndRdn<ID> key = rdnIdx.reverseLookup( childId );
 
-            Entry entry = lookup( id );
-            
-            return entry != null; 
-        }
-        catch ( LdapException e )
+        if ( key == null )
         {
-            return false;
+            return null;
         }
+
+        return key.getParentId();
     }
 
 
-    // ------------------------------------------------------------------------
-    // Index Operations
-    // ------------------------------------------------------------------------
     /**
      * Retrieve the SuffixID
      */
@@ -1050,27 +2036,74 @@ public abstract class AbstractBTreeParti
     }
 
 
+    //------------------------------------------------------------------------
+    // Index handling
+    //------------------------------------------------------------------------
     /**
      * {@inheritDoc}
      */
-    public boolean isSyncOnWrite()
+    public void addIndex( Index<?, Entry, ID> index ) throws Exception
     {
-        return isSyncOnWrite.get();
+        checkInitialized( "addIndex" );
+
+        // Check that the index ID is valid
+        AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() );
+
+        if ( attributeType == null )
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_309, index.getAttributeId() ) );
+        }
+
+        String oid = attributeType.getOid();
+
+        if ( SYS_INDEX_OIDS.contains( oid ) )
+        {
+            if ( !systemIndices.containsKey( oid ) )
+            {
+                systemIndices.put( oid, index );
+            }
+        }
+        else
+        {
+            if ( !userIndices.containsKey( oid ) )
+            {
+                userIndices.put( oid, index );
+            }
+        }
     }
 
+
     /**
-     * {@inheritDoc}
+     * Add some new indexes
+     * @param indexes The added indexes
      */
-    public void setSyncOnWrite( boolean isSyncOnWrite )
+    public void addIndexedAttributes( Index<?, Entry, ID>... indexes )
     {
-        checkInitialized( "syncOnWrite" );
-        this.isSyncOnWrite.set( isSyncOnWrite );
+        for ( Index<?, Entry, ID> index : indexes )
+        {
+            indexedAttributes.add( index );
+        }
+    }
+
+    
+    /**
+     * Set the list of indexes for this partition
+     * @param indexedAttributes The list of indexes
+     */
+    public void setIndexedAttributes( Set<Index<?, Entry, ID>> indexedAttributes )
+    {
+        this.indexedAttributes = indexedAttributes;
     }
 
 
-    //------------------------------------------------------------------------
-    // Index handling
-    //------------------------------------------------------------------------
+    /**
+     * @return The list of indexed attributes
+     */
+    public Set<Index<?, Entry, ID>> getIndexedAttributes()
+    {
+        return indexedAttributes;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -1279,18 +2312,6 @@ public abstract class AbstractBTreeParti
     }
     
     
-    /**
-     * {@inheritDoc}}
-     */
-    public abstract ID getDefaultId();
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public abstract ID getRootId();
-    
-    
     //---------------------------------------------------------------------------------------------
     // Alias index manipulation
     //---------------------------------------------------------------------------------------------
@@ -1411,6 +2432,131 @@ public abstract class AbstractBTreeParti
     }
     
     
+    /**
+     * Removes the index entries for an alias before the entry is deleted from
+     * the master table.
+     *
+     * @todo Optimize this by walking the hierarchy index instead of the name
+     * @param aliasId the id of the alias entry in the master table
+     * @throws LdapException if we cannot parse ldap names
+     * @throws Exception if we cannot delete index values in the database
+     */
+    protected void dropAliasIndices( ID aliasId ) throws Exception
+    {
+        String targetDn = aliasIdx.reverseLookup( aliasId );
+        ID targetId = getEntryId( new Dn( schemaManager, targetDn ) );
+
+        if ( targetId == null )
+        {
+            // the entry doesn't exist, probably it has been deleted or renamed
+            // TODO: this is just a workaround for now, the alias indices should be updated when target entry is deleted or removed
+            return;
+        }
+
+        Dn aliasDn = getEntryDn( aliasId );
+
+        Dn ancestorDn = aliasDn.getParent();
+        ID ancestorId = getEntryId( ancestorDn );
+
+        /*
+         * We cannot just drop all tuples in the one level and subtree userIndices
+         * linking baseIds to the targetId.  If more than one alias refers to
+         * the target then droping all tuples with a value of targetId would
+         * make all other aliases to the target inconsistent.
+         *
+         * We need to walk up the path of alias ancestors until we reach the
+         * upSuffix, deleting each ( ancestorId, targetId ) tuple in the
+         * subtree scope alias.  We only need to do this for the direct parent
+         * of the alias on the one level subtree.
+         */
+        oneAliasIdx.drop( ancestorId, targetId );
+        subAliasIdx.drop( ancestorId, targetId );
+
+        while ( !ancestorDn.equals( suffixDn ) && ancestorDn.size() > suffixDn.size() )
+        {
+            ancestorDn = ancestorDn.getParent();
+            ancestorId = getEntryId( ancestorDn );
+
+            subAliasIdx.drop( ancestorId, targetId );
+        }
+
+        // Drops all alias tuples pointing to the id of the alias to be deleted
+        aliasIdx.drop( aliasId );
+    }
+
+
+    /**
+     * For all aliases including and under the moved base, this method removes
+     * one and subtree alias index tuples for old ancestors above the moved base
+     * that will no longer be ancestors after the move.
+     *
+     * @param movedBase the base at which the move occured - the moved node
+     * @throws Exception if system userIndices fail
+     */
+    protected void dropMovedAliasIndices( final Dn movedBase ) throws Exception
+    {
+        ID movedBaseId = getEntryId( movedBase );
+
+        if ( aliasIdx.reverseLookup( movedBaseId ) != null )
+        {
+            dropAliasIndices( movedBaseId, movedBase );
+        }
+    }
+
+
+    /**
+     * For the alias id all ancestor one and subtree alias tuples are moved
+     * above the moved base.
+     *
+     * @param aliasId the id of the alias
+     * @param movedBase the base where the move occured
+     * @throws Exception if userIndices fail
+     */
+    protected void dropAliasIndices( ID aliasId, Dn movedBase ) throws Exception
+    {
+        String targetDn = aliasIdx.reverseLookup( aliasId );
+        ID targetId = getEntryId( new Dn( schemaManager, targetDn ) );
+        Dn aliasDn = getEntryDn( aliasId );
+
+        /*
+         * Start droping index tuples with the first ancestor right above the
+         * moved base.  This is the first ancestor effected by the move.
+         */
+        Dn ancestorDn = new Dn( schemaManager, movedBase.getRdn( movedBase.size() - 1 ) );
+        ID ancestorId = getEntryId( ancestorDn );
+
+        /*
+         * We cannot just drop all tuples in the one level and subtree userIndices
+         * linking baseIds to the targetId.  If more than one alias refers to
+         * the target then droping all tuples with a value of targetId would
+         * make all other aliases to the target inconsistent.
+         *
+         * We need to walk up the path of alias ancestors right above the moved
+         * base until we reach the upSuffix, deleting each ( ancestorId,
+         * targetId ) tuple in the subtree scope alias.  We only need to do
+         * this for the direct parent of the alias on the one level subtree if
+         * the moved base is the alias.
+         */
+        if ( aliasDn.equals( movedBase ) )
+        {
+            oneAliasIdx.drop( ancestorId, targetId );
+        }
+
+        subAliasIdx.drop( ancestorId, targetId );
+
+        while ( !ancestorDn.equals( suffixDn ) )
+        {
+            ancestorDn = new Dn( schemaManager, ancestorDn.getRdn( ancestorDn.size() - 1 ) );
+            ancestorId = getEntryId( ancestorDn );
+
+            subAliasIdx.drop( ancestorId, targetId );
+        }
+    }
+
+    
+    //---------------------------------------------------------------------------------------------
+    // Debug methods
+    //---------------------------------------------------------------------------------------------
     private void dumpIndex( OutputStream stream, Index<?, Entry, ID> index )
     {
         try
@@ -1457,4 +2603,13 @@ public abstract class AbstractBTreeParti
             stream.write( Strings.getBytesUtf8( "Cannot find an index for AttributeType names " + name ) );
         }
     }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return "Partition<" + id + ">";
+    }
 }



Mime
View raw message