You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by se...@apache.org on 2010/05/01 18:25:20 UTC
svn commit: r940073 [2/2] - in /directory/apacheds/trunk:
avl-partition/src/main/java/org/apache/directory/server/core/partition/avl/
avl-partition/src/test/java/org/apache/directory/server/core/partition/avl/
jdbm-store/src/main/java/org/apache/direct...
Modified: directory/apacheds/trunk/xdbm-base/src/main/java/org/apache/directory/server/xdbm/AbstractStore.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/xdbm-base/src/main/java/org/apache/directory/server/xdbm/AbstractStore.java?rev=940073&r1=940072&r2=940073&view=diff
==============================================================================
--- directory/apacheds/trunk/xdbm-base/src/main/java/org/apache/directory/server/xdbm/AbstractStore.java (original)
+++ directory/apacheds/trunk/xdbm-base/src/main/java/org/apache/directory/server/xdbm/AbstractStore.java Sat May 1 16:25:19 2010
@@ -21,9 +21,11 @@ package org.apache.directory.server.xdbm
import java.io.File;
+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;
@@ -32,13 +34,21 @@ import org.apache.directory.server.core.
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.shared.asn1.primitives.OID;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
+import org.apache.directory.shared.ldap.cursor.Cursor;
import org.apache.directory.shared.ldap.entry.Entry;
import org.apache.directory.shared.ldap.entry.EntryAttribute;
+import org.apache.directory.shared.ldap.entry.Modification;
+import org.apache.directory.shared.ldap.entry.ModificationOperation;
import org.apache.directory.shared.ldap.entry.Value;
import org.apache.directory.shared.ldap.exception.LdapAliasDereferencingException;
import org.apache.directory.shared.ldap.exception.LdapAliasException;
import org.apache.directory.shared.ldap.exception.LdapException;
+import org.apache.directory.shared.ldap.exception.LdapNoSuchObjectException;
+import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
+import org.apache.directory.shared.ldap.message.ResultCodeEnum;
+import org.apache.directory.shared.ldap.name.AVA;
import org.apache.directory.shared.ldap.name.DN;
+import org.apache.directory.shared.ldap.name.RDN;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.directory.shared.ldap.schema.MatchingRule;
import org.apache.directory.shared.ldap.schema.SchemaManager;
@@ -61,6 +71,12 @@ public abstract class AbstractStore<E, I
/** The default cache size is set to 10 000 objects */
public static final int DEFAULT_CACHE_SIZE = 10000;
+ /** Static declarations to avoid lookup all over the code */
+ protected static AttributeType OBJECT_CLASS_AT;
+ protected static AttributeType ENTRY_CSN_AT;
+ protected static AttributeType ENTRY_UUID_AT;
+ protected static AttributeType ALIASED_OBJECT_NAME_AT;
+
/** true if initialized */
protected boolean initialized;
@@ -82,6 +98,9 @@ public abstract class AbstractStore<E, I
/** A pointer on the schemaManager */
protected SchemaManager schemaManager;
+ /** the master table storing entries by primary key */
+ protected MasterTable<ID, Entry> master;
+
/** a map of attributeType numeric ID to user userIndices */
protected Map<String, Index<?, E, ID>> userIndices = new HashMap<String, Index<?, E, ID>>();
@@ -206,8 +225,26 @@ public abstract class AbstractStore<E, I
}
+ /**
+ * {@inheritDoc}
+ */
+ public void setProperty( String propertyName, String propertyValue ) throws Exception
+ {
+ master.setProperty( propertyName, propertyValue );
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getProperty( String propertyName ) throws Exception
+ {
+ return master.getProperty( propertyName );
+ }
+
+
//------------------------------------------------------------------------
- // Index
+ // Index handling
//------------------------------------------------------------------------
/**
@@ -324,6 +361,26 @@ public abstract class AbstractStore<E, I
/**
+ * Gets the root ID of this store implementation.
+ *
+ * @return the root ID
+ */
+ protected abstract ID getRootId();
+
+
+ /**
+ * Gets the root ID of this store implementation.
+ *
+ * @return the root ID
+ */
+ protected ID getSuffixId() throws Exception
+ {
+ // TODO: optimize
+ return getEntryId( getSuffixDn() );
+ }
+
+
+ /**
* {@inheritDoc}
*/
public Iterator<String> userIndices()
@@ -590,7 +647,653 @@ public abstract class AbstractStore<E, I
}
- ////////////////////////////////////////////////7
+ /**
+ * {@inheritDoc}
+ */
+ public int count() throws Exception
+ {
+ return master.count();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getChildCount( ID id ) throws Exception
+ {
+ return oneLevelIdx.count( id );
+ }
+
+
+ //------------------------------------------------------------------------
+ // DN and ID handling
+ //------------------------------------------------------------------------
+
+ /**
+ * {@inheritDoc}
+ */
+ public ID getEntryId( DN dn ) throws Exception
+ {
+ if ( !dn.isNormalized() )
+ {
+ dn.normalize( schemaManager.getNormalizerMapping() );
+ // TODO: force normalized DN
+ //throw new IllegalArgumentException( I18n.err( I18n.ERR_218, suffixDn.getName() ) );
+ }
+
+ int dnSize = dn.size();
+ int i = suffixDn.size();
+
+ ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( getRootId(), suffixDn.getRdns() );
+
+ ID curEntryId = rdnIdx.forwardLookup( key );
+
+ for ( ; i < dnSize; i++ )
+ {
+ key = new ParentIdAndRdn<ID>( curEntryId, dn.getRdn( i ) );
+ curEntryId = rdnIdx.forwardLookup( key );
+ if ( curEntryId == null )
+ {
+ break;
+ }
+ }
+
+ return curEntryId;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public DN getEntryDn( ID id ) throws Exception
+ {
+ return buildEntryDn( id );
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ID getParentId( ID childId ) throws Exception
+ {
+ ParentIdAndRdn<ID> key = rdnIdx.reverseLookup( childId );
+ if ( key == null )
+ {
+ return null;
+ }
+
+ return key.getParentId();
+ }
+
+
+ //------------------------------------------------------------------------
+ // Operations
+ //------------------------------------------------------------------------
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry lookup( ID id ) throws Exception
+ {
+ Entry entry = master.get( id );
+
+ if ( entry != null )
+ {
+ DN dn = buildEntryDn( id );
+ entry.setDn( dn );
+ return entry;
+ }
+
+ return null;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public IndexCursor<ID, E, ID> list( ID id ) throws Exception
+ {
+ IndexCursor<ID, E, ID> cursor = oneLevelIdx.forwardCursor( id );
+ cursor.beforeValue( id, null );
+ return cursor;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * TODO : We should be able to revert all the changes made to index
+ * if something went wrong. Also the index should auto-repair : if
+ * an entry does not exist in the Master table, then the index must be updated to reflect this.
+ */
+ @SuppressWarnings("unchecked")
+ public synchronized void add( Entry entry ) throws Exception
+ {
+ if ( entry instanceof ClonedServerEntry )
+ {
+ throw new Exception( I18n.err( I18n.ERR_215 ) );
+ }
+
+ ID parentId;
+ ID id = master.getNextId();
+
+ //
+ // Suffix entry cannot have a parent since it is the root so it is
+ // capped off using the zero value which no entry can have since
+ // entry sequences start at 1.
+ //
+ DN entryDn = entry.getDn();
+ DN parentDn = null;
+ ParentIdAndRdn<ID> key = null;
+
+ if ( entryDn.equals( suffixDn ) )
+ {
+ parentId = getRootId();
+ key = new ParentIdAndRdn<ID>( parentId, suffixDn.getRdns() );
+ }
+ else
+ {
+ parentDn = entryDn.getParent();
+ parentId = getEntryId( parentDn );
+ key = new ParentIdAndRdn<ID>( parentId, entryDn.getRdn() );
+ }
+
+ // don't keep going if we cannot find the parent Id
+ if ( parentId == null )
+ {
+ throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_216, parentDn ) );
+ }
+
+ rdnIdx.add( key, id );
+
+ EntryAttribute objectClass = entry.get( OBJECT_CLASS_AT );
+
+ if ( objectClass == null )
+ {
+ String msg = I18n.err( I18n.ERR_217, entryDn.getName(), entry );
+ ResultCodeEnum rc = ResultCodeEnum.OBJECT_CLASS_VIOLATION;
+ LdapSchemaViolationException e = new LdapSchemaViolationException( rc, msg );
+ //e.setResolvedName( entryDn );
+ throw e;
+ }
+
+ // Start adding the system userIndices
+ // Why bother doing a lookup if this is not an alias.
+ // First, the ObjectClass index
+ for ( Value<?> value : objectClass )
+ {
+ objectClassIdx.add( value.getString(), id );
+ }
+
+ if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
+ {
+ EntryAttribute aliasAttr = entry.get( ALIASED_OBJECT_NAME_AT );
+ addAliasIndices( id, entryDn, aliasAttr.getString() );
+ }
+
+ if ( !Character.isDigit( entryDn.getNormName().charAt( 0 ) ) )
+ {
+ throw new IllegalStateException( I18n.err( I18n.ERR_218, entryDn.getNormName() ) );
+ }
+
+ oneLevelIdx.add( parentId, id );
+
+ // Update the EntryCsn index
+ EntryAttribute entryCsn = entry.get( ENTRY_CSN_AT );
+
+ if ( entryCsn == null )
+ {
+ String msg = I18n.err( I18n.ERR_219, entryDn.getName(), entry );
+ throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg );
+ }
+
+ entryCsnIdx.add( entryCsn.getString(), id );
+
+ // Update the EntryUuid index
+ EntryAttribute entryUuid = entry.get( ENTRY_UUID_AT );
+
+ if ( entryUuid == null )
+ {
+ String msg = I18n.err( I18n.ERR_220, entryDn.getName(), entry );
+ throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg );
+ }
+
+ entryUuidIdx.add( entryUuid.getString(), id );
+
+ ID tempId = parentId;
+
+ while ( ( tempId != null ) && ( !tempId.equals( getRootId() ) ) && ( !tempId.equals( getSuffixId() ) ) )
+ {
+ subLevelIdx.add( tempId, id );
+ tempId = getParentId( tempId );
+ }
+
+ // making entry an ancestor/descendent of itself in sublevel index
+ subLevelIdx.add( id, id );
+
+ // Now work on the user defined userIndices
+ for ( EntryAttribute attribute : entry )
+ {
+ String attributeOid = attribute.getAttributeType().getOid();
+
+ if ( hasUserIndexOn( attributeOid ) )
+ {
+ Index<Object, E, ID> idx = ( Index<Object, E, ID> ) getUserIndex( attributeOid );
+
+ // 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 )
+ {
+ idx.add( value.get(), id );
+ }
+
+ // Adds only those attributes that are indexed
+ presenceIdx.add( attributeOid, id );
+ }
+ }
+
+ master.put( id, entry );
+
+ if ( isSyncOnWrite )
+ {
+ sync();
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void modify( DN dn, ModificationOperation modOp, Entry mods ) throws Exception
+ {
+ if ( mods instanceof ClonedServerEntry )
+ {
+ throw new Exception( I18n.err( I18n.ERR_215 ) );
+ }
+
+ ID id = getEntryId( dn );
+ Entry entry = master.get( id );
+
+ for ( AttributeType attributeType : mods.getAttributeTypes() )
+ {
+ EntryAttribute attr = mods.get( attributeType );
+
+ switch ( modOp )
+ {
+ case ADD_ATTRIBUTE:
+ add( id, entry, attr );
+ break;
+
+ case REMOVE_ATTRIBUTE:
+ remove( id, entry, attr );
+ break;
+
+ case REPLACE_ATTRIBUTE:
+ replace( id, entry, attr );
+
+ break;
+
+ default:
+ throw new LdapException( I18n.err( I18n.ERR_221 ) );
+ }
+ }
+
+ master.put( id, entry );
+
+ if ( isSyncOnWrite )
+ {
+ sync();
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void modify( DN dn, List<Modification> mods ) throws Exception
+ {
+ ID id = getEntryId( dn );
+ Entry entry = master.get( id );
+
+ for ( Modification mod : mods )
+ {
+ EntryAttribute attrMods = mod.getAttribute();
+
+ switch ( mod.getOperation() )
+ {
+ case ADD_ATTRIBUTE:
+ add( id, entry, attrMods );
+ break;
+
+ case REMOVE_ATTRIBUTE:
+ remove( id, entry, attrMods );
+ break;
+
+ case REPLACE_ATTRIBUTE:
+ replace( id, entry, attrMods );
+ break;
+
+ default:
+ throw new LdapException( I18n.err( I18n.ERR_221 ) );
+ }
+ }
+
+ master.put( id, entry );
+
+ if ( isSyncOnWrite )
+ {
+ sync();
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public synchronized void delete( ID id ) throws Exception
+ {
+ Entry entry = master.get( id );
+ ID parentId = getParentId( id );
+
+ EntryAttribute objectClass = entry.get( OBJECT_CLASS_AT );
+
+ if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
+ {
+ dropAliasIndices( id );
+ }
+
+ for ( Value<?> value : objectClass )
+ {
+ objectClassIdx.drop( value.getString(), id );
+ }
+
+ rdnIdx.drop( id );
+ oneLevelIdx.drop( id );
+ entryCsnIdx.drop( id );
+ entryUuidIdx.drop( id );
+
+ if ( !id.equals( getSuffixId() ) )
+ {
+ subLevelIdx.drop( id );
+ }
+
+ // Remove parent's reference to entry only if entry is not the suffix
+ if ( !parentId.equals( getRootId() ) )
+ {
+ // TODO: is this duplicate to oneLevelIdx.drop( id ) ???
+ oneLevelIdx.drop( parentId, id );
+ }
+
+ for ( EntryAttribute attribute : entry )
+ {
+ String attributeOid = attribute.getAttributeType().getOid();
+
+ if ( hasUserIndexOn( attributeOid ) )
+ {
+ Index<?, E, ID> index = getUserIndex( attributeOid );
+
+ // 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.get(), id );
+ }
+
+ presenceIdx.drop( attributeOid, id );
+ }
+ }
+
+ master.delete( id );
+
+ if ( isSyncOnWrite )
+ {
+ sync();
+ }
+ }
+
+
+ /**
+ * Changes the relative distinguished name of an entry specified by a
+ * distinguished name with the optional removal of the old RDN attribute
+ * value from the entry. Name changes propagate down as dn changes to the
+ * descendants of the entry where the RDN changed.
+ *
+ * An RDN change operation does not change parent child relationships. It
+ * merely propagates a name change at a point in the DIT where the RDN is
+ * changed. The change propagates down the subtree rooted at the
+ * distinguished name specified.
+ *
+ * @param dn the normalized distinguished name of the entry to alter
+ * @param newRdn the new RDN to set
+ * @param deleteOldRdn whether or not to remove the old RDN attr/val
+ * @throws Exception if there are any errors propagating the name changes
+ */
+ @SuppressWarnings("unchecked")
+ public void rename( DN dn, RDN newRdn, boolean deleteOldRdn ) throws Exception
+ {
+ ID id = getEntryId( dn );
+ Entry entry = lookup( id );
+ DN updn = entry.getDn();
+
+ newRdn.normalize( schemaManager.getNormalizerMapping() );
+
+ /*
+ * 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().get();
+ AttributeType newRdnAttrType = schemaManager.lookupAttributeTypeRegistry( newNormType );
+
+ entry.add( newRdnAttrType, newAtav.getUpValue() );
+
+ if ( hasUserIndexOn( newNormType ) )
+ {
+ Index<?, E, ID> index = getUserIndex( newNormType );
+ ( ( 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( oldNormType ) )
+ {
+ Index<?, E, ID> index = getUserIndex( oldNormType );
+ ( ( 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 )
+ {
+ sync();
+ }
+ }
+
+
+ /*
+ * The move operation severs a child from a parent creating a new parent
+ * child relationship. As a consequence the relationships between the
+ * old ancestors of the child and its descendants change. A descendant is
+ *
+ */
+
+ public void move( DN oldChildDn, DN newParentDn, RDN newRdn, boolean deleteOldRdn ) throws Exception
+ {
+ ID childId = getEntryId( oldChildDn );
+ rename( oldChildDn, newRdn, deleteOldRdn );
+ DN newUpdn = move( oldChildDn, childId, newParentDn );
+
+ // Update the current entry
+ Entry entry = master.get( childId );
+ entry.setDn( newUpdn );
+ master.put( childId, entry );
+
+ if ( isSyncOnWrite )
+ {
+ sync();
+ }
+ }
+
+
+ public void move( DN oldChildDn, DN newParentDn ) throws Exception
+ {
+ ID childId = getEntryId( oldChildDn );
+ DN newUpdn = move( oldChildDn, childId, newParentDn );
+
+ // Update the current entry
+ Entry entry = master.get( childId );
+ entry.setDn( newUpdn );
+ master.put( childId, entry );
+
+ if ( isSyncOnWrite )
+ {
+ sync();
+ }
+ }
+
+
+ //------------------------------------------------------------------------
+ // Helpers
+ //------------------------------------------------------------------------
+
+ /**
+ * 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
+ {
+ DN dn = new DN();
+
+ ID parentId = id;
+
+ do
+ {
+ ParentIdAndRdn<ID> cur = rdnIdx.reverseLookup( parentId );
+ RDN[] rdns = cur.getRdns();
+ for ( RDN rdn : rdns )
+ {
+ dn.addNormalizedInOrder( rdn );
+ }
+ parentId = cur.getParentId();
+ }
+ while ( !parentId.equals( getRootId() ) );
+
+ return dn;
+ }
+
+
+ /**
+ *
+ * contructs a normalized DN using the RDN index. This is useful
+ * to identify in cases like finding the parent entry's id
+ * (cause that will be stored in the RDN of the entry identified by the given DN in string form)
+ *
+ * @param dn the DN of the entry in string form
+ * @return DN object build after fetching all the RDNs from RDN index
+ * @throws Exception
+ */
+ protected DN buildEntryDn( String dn ) throws Exception
+ {
+ // TODO: what is this for???
+ DN normDN = new DN( dn );
+ normDN.normalize( schemaManager.getNormalizerMapping() );
+
+ int dnSize = normDN.size();
+ int i = suffixDn.size();
+
+ ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( getRootId(), suffixDn.getRdns() );
+
+ ID curEntryId = rdnIdx.forwardLookup( key );
+
+ for ( ; i < dnSize; i++ )
+ {
+ key = new ParentIdAndRdn<ID>( curEntryId, normDN.getRdn( i ) );
+ curEntryId = rdnIdx.forwardLookup( key );
+ }
+
+ return normDN;
+ }
+
/**
* Adds a set of attribute values while affecting the appropriate userIndices.
@@ -1019,6 +1722,7 @@ public abstract class AbstractStore<E, I
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
@@ -1107,4 +1811,170 @@ public abstract class AbstractStore<E, I
subAliasIdx.drop( ancestorId, targetId );
}
}
+
+
+ /**
+ * Recursively modifies the distinguished name of an entry and the names of
+ * its descendants calling itself in the recursion.
+ *
+ * @param id the primary key of the entry
+ * @param updn User provided distinguished name to set as the new DN
+ * @param isMove whether or not the name change is due to a move operation
+ * which affects alias userIndices.
+ * @throws Exception if something goes wrong
+ */
+ protected void modifyDn( ID id, DN updn, boolean isMove ) throws Exception
+ {
+ String aliasTarget;
+
+ //updated the RDN index
+ rdnIdx.drop( id );
+ if ( !updn.isNormalized() )
+ {
+ // just normalize the RDN alone
+ updn.getRdn().normalize( schemaManager.getNormalizerMapping() );
+ }
+
+ ID parentId = getEntryId( updn.getParent() );
+ ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( parentId, updn.getRdn() );
+ rdnIdx.add( key, id );
+
+ /*
+ * 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.
+ */
+ if ( isMove )
+ {
+ aliasTarget = aliasIdx.reverseLookup( id );
+
+ if ( null != aliasTarget )
+ {
+ addAliasIndices( id, buildEntryDn( id ), aliasTarget );
+ }
+ }
+ }
+
+
+ /**
+ * 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 oldChildDn the normalized dn of the child to be moved
+ * @param childId the id of the child being moved
+ * @param newParentDn the normalized dn of the new parent for the child
+ * @throws Exception if something goes wrong
+ */
+ protected DN move( DN oldChildDn, ID childId, DN newParentDn ) throws Exception
+ {
+ // Get the child and the new parent to be entries and Ids
+ ID newParentId = getEntryId( newParentDn );
+ 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( oldChildDn );
+
+ /*
+ * 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 );
+
+ /*
+ * Build the new user provided DN (updn) for the child using the child's
+ * user provided RDN & the new parent's UPDN. Basically add the child's
+ * UpRdn String to the tail of the new parent's Updn Name.
+ */
+ DN childUpdn = buildEntryDn( childId );
+ RDN childRdn = childUpdn.getRdn( childUpdn.size() - 1 );
+ DN newUpdn = buildEntryDn( newParentId );
+ newUpdn.add( childRdn );
+
+ // Call the modifyDn operation with the new updn
+ modifyDn( childId, newUpdn, true );
+
+ return newUpdn;
+ }
+
+
+ /**
+ * Updates the SubLevel Index as part of a move operation.
+ *
+ * @param childId child id to be moved
+ * @param oldParentId old parent's id
+ * @param newParentId new parent's id
+ * @throws Exception
+ */
+ protected void updateSubLevelIndex( ID childId, 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, E, ID>> cursor = subLevelIdx.forwardCursor( childId );
+
+ List<ID> childIds = new ArrayList<ID>();
+ childIds.add( childId );
+
+ 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 );
+ }
+ }
+ }
+
}