blob: 665f2bcadac1afa3b926c8164d988b33791893ca [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.server.core.partition.impl.btree.jdbm;
import java.io.File;
import java.io.IOException;
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 javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import jdbm.RecordManager;
import jdbm.helper.MRU;
import jdbm.recman.BaseRecordManager;
import jdbm.recman.CacheRecordManager;
import org.apache.directory.server.core.partition.Oid;
import org.apache.directory.server.core.partition.impl.btree.Index;
import org.apache.directory.server.core.partition.impl.btree.IndexAssertion;
import org.apache.directory.server.core.partition.impl.btree.IndexAssertionEnumeration;
import org.apache.directory.server.core.partition.impl.btree.IndexConfiguration;
import org.apache.directory.server.core.partition.impl.btree.IndexNotFoundException;
import org.apache.directory.server.core.partition.impl.btree.IndexRecord;
import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
import org.apache.directory.server.schema.registries.OidRegistry;
import org.apache.directory.server.schema.registries.Registries;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException;
import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
import org.apache.directory.shared.ldap.message.AttributeImpl;
import org.apache.directory.shared.ldap.message.AttributesImpl;
import org.apache.directory.shared.ldap.message.ModificationItemImpl;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.directory.shared.ldap.util.AttributeUtils;
import org.apache.directory.shared.ldap.util.NamespaceTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JdbmStore
{
private static final Logger log = LoggerFactory.getLogger( JdbmStore.class );
/** The default cache size is set to 10 000 objects */
private static final int DEFAULT_CACHE_SIZE = 10000;
/** the JDBM record manager used by this database */
private RecordManager recMan;
/** the normalized suffix DN of this backend database */
private LdapDN normSuffix;
/** the user provided suffix DN of this backend database */
private LdapDN upSuffix;
/** the working directory to use for files */
private File workingDirectory;
/** the master table storing entries by primary key */
private JdbmMasterTable master;
/** a map of attribute names to user indices */
private Map<String, Index> indices;
/** a map of index names to system indices */
private Map<String, Index> sysIndices;
/** true if open */
private boolean initialized;
/** true if we sync disks on every write operation */
private boolean isSyncOnWrite = true;
/**
* Declaration of the system indices we need in the server
*/
/** the normalized distinguished name index */
private Index ndnIdx;
/** the user provided distinguished name index */
private Index updnIdx;
/** the attribute existance index */
private Index existanceIdx;
/** the parent child relationship index */
private Index hierarchyIdx;
/** the one level scope alias index */
private Index oneAliasIdx;
/** the subtree scope alias index */
private Index subAliasIdx;
/** a system index on aliasedObjectName attribute */
private Index aliasIdx;
/** Two static declaration to avoid lookup all over the code */
private static AttributeType OBJECT_CLASS_AT;
private static AttributeType ALIASED_OBJECT_NAME_AT;
/** A pointer on the AT registry */
private AttributeTypeRegistry attributeTypeRegistry;
/** A pointer on the OID registry */
private OidRegistry oidRegistry;
// ------------------------------------------------------------------------
// C O N S T R U C T O R S
// ------------------------------------------------------------------------
/**
* Creates a store based on JDBM B+Trees.
*/
public JdbmStore()
{
}
/**
* Initialize the user declared indices
*/
private void initCustomIndex( Object index, Set<String> sysOidSet, Set<String> customAddedSystemIndices ) throws NamingException
{
String name = null;
int cacheSize = IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE;
int numDupLimit = IndexConfiguration.DEFAULT_DUPLICATE_LIMIT;
// no custom cacheSize info is available so default sticks
if ( index instanceof String )
{
name = ( String )index;
if ( log.isDebugEnabled() )
{
log.debug( "Using default cache size of {} for index on attribute {}",
new Integer( cacheSize ), name );
}
}
else if ( index instanceof IndexConfiguration )
{
// custom cache size is used
IndexConfiguration indexConfiguration = ( IndexConfiguration )index;
name = indexConfiguration.getAttributeId();
cacheSize = indexConfiguration.getCacheSize();
numDupLimit = indexConfiguration.getDuplicateLimit();
if ( cacheSize <= 0 )
{
log.warn( "Cache size {} for index on attribute is null or negative. Using default value.",
new Integer(cacheSize), name );
cacheSize = IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE;
}
else
{
log.info( "Using cache size of {} for index on attribute {}",
new Integer( cacheSize ), name );
}
if ( cacheSize <= 0 )
{
log.warn( "Duplicate limit {} for index on attribute is null or negative. Using default value.",
new Integer(numDupLimit), name );
cacheSize = IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE;
}
else
{
log.info( "Using duplicate limit of {} for index on attribute {}",
new Integer( numDupLimit ), name );
}
}
String oid = oidRegistry.getOid( name );
AttributeType type = attributeTypeRegistry.lookup( oid );
// check if attribute is a system attribute
if ( sysOidSet.contains( oid ) )
{
if ( oid.equals( Oid.EXISTANCE ) )
{
setExistanceIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.EXISTANCE );
}
else if ( oid.equals( Oid.HIERARCHY ) )
{
setHierarchyIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.HIERARCHY );
}
else if ( oid.equals( Oid.UPDN ) )
{
setUpdnIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.UPDN );
}
else if ( oid.equals( Oid.NDN ) )
{
setNdnIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.NDN );
}
else if ( oid.equals( Oid.ONEALIAS ) )
{
setOneAliasIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.ONEALIAS );
}
else if ( oid.equals( Oid.SUBALIAS ) )
{
setSubAliasIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.SUBALIAS);
}
else if ( oid.equals( Oid.ALIAS ) )
{
setAliasIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.ALIAS );
}
else
{
throw new NamingException( "Unidentified system index " + oid );
}
}
else
{
addIndexOn( type, cacheSize, numDupLimit );
}
}
/**
* Initialize the system indices
*/
private void initSystemIndices( String systemIndexName, Set<String> customAddedSystemIndices ) throws NamingException
{
if ( ! customAddedSystemIndices.contains( systemIndexName ) )
{
AttributeType type = attributeTypeRegistry.lookup( systemIndexName );
if ( log.isDebugEnabled() )
{
log.debug( "Using default cache size of {} for index on attribute {}",
new Integer( IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE ), systemIndexName );
}
if ( systemIndexName.equals( Oid.EXISTANCE ) )
{
setExistanceIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.HIERARCHY ) )
{
setHierarchyIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.UPDN ) )
{
setUpdnIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.NDN ) )
{
setNdnIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.ONEALIAS ) )
{
setOneAliasIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.SUBALIAS ) )
{
setSubAliasIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.ALIAS ) )
{
setAliasIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else
{
throw new NamingException( "Unidentified system index " + systemIndexName );
}
}
}
/**
* Use this method to initialize the indices. Only call this after
* the registries and the optimizer have been enabled. The '2' at the end
* shows this is the 3rd init method called in the init sequence.
*
* @param indices The set of indices to initialize, found in the configuration file.
* @throws NamingException
*/
protected void initIndices2( Set indices ) throws NamingException
{
Set<String> sysOidSet = new HashSet<String>();
sysOidSet.add( Oid.EXISTANCE );
sysOidSet.add( Oid.HIERARCHY );
sysOidSet.add( Oid.UPDN );
sysOidSet.add( Oid.NDN );
sysOidSet.add( Oid.ONEALIAS );
sysOidSet.add( Oid.SUBALIAS );
sysOidSet.add( Oid.ALIAS );
// Used to calculate the system indices we must automatically add
Set<String> customAddedSystemIndices = new HashSet<String>();
for ( Object indexName:indices )
{
/*
* NOTE
* ====
*
* The object returned by the indexedAttributes property
* of the configuration may include just a simple set of <String>
* names for the attributes being index OR may include a set
* of IndexConfiguration objects.
*
* If the objects are strings extra information about the
* cacheSize of an index is not available and so the default is
* used. If an IndexConfiguration is available then the custom
* cacheSize is used.
*/
initCustomIndex( indexName, sysOidSet, customAddedSystemIndices );
}
// -------------------------------------------------------------------
// Add all system indices that were not custom configured by iterating
// through all system index oids and checking of that index is
// contained within the customAddedSystemIndices set. If it is not
// contained in this set then the system index was not custom
// configured above and must be configured with defaults below.
// -------------------------------------------------------------------
for ( String systemIndexName:sysOidSet )
{
initSystemIndices( systemIndexName, customAddedSystemIndices );
}
}
/**
* Called last (4th) to check if the suffix entry has been created on disk,
* and if not it is created.
*
* @param suffix
* @param entry
* @throws NamingException
*/
protected void initSuffixEntry3( String suffix, Attributes entry ) throws NamingException
{
// add entry for context, if it does not exist
Attributes suffixOnDisk = getSuffixEntry();
if ( suffixOnDisk == null )
{
LdapDN dn = new LdapDN( suffix );
LdapDN normalizedSuffix = LdapDN.normalize( dn, attributeTypeRegistry.getNormalizerMapping() );
add( normalizedSuffix, entry );
}
}
/**
* Initialize the JDBM storage system.
*
* @param config The configuration conatining all the information about indices, cahce, etc.
* @throws NamingException If we get some error
*/
public synchronized void init( JdbmStoreConfiguration config ) throws NamingException
{
isSyncOnWrite = config.isSyncOnWrite();
oidRegistry = config.getOidRegistry();
attributeTypeRegistry = config.getAttributeTypeRegistry();
OBJECT_CLASS_AT = attributeTypeRegistry.lookup( SchemaConstants.OBJECT_CLASS_AT );
ALIASED_OBJECT_NAME_AT = attributeTypeRegistry.lookup( SchemaConstants.ALIASED_OBJECT_NAME_AT );
this.upSuffix = new LdapDN( config.getSuffixDn() );
this.normSuffix = LdapDN.normalize( upSuffix, attributeTypeRegistry.getNormalizerMapping() );
workingDirectory = config.getWorkingDirectory();
workingDirectory.mkdirs();
try
{
// First, check if the file storing the data exists
String path = workingDirectory.getPath() + File.separator + "master";
BaseRecordManager base = new BaseRecordManager( path );
base.disableTransactions();
int cacheSize = config.getCacheSize();
if ( cacheSize < 0 )
{
cacheSize = DEFAULT_CACHE_SIZE;
if ( log.isDebugEnabled() )
{
log.debug( "Using the default entry cache size of {} for {} partition",
new Integer( cacheSize ), config.getName() );
}
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( "Using the custom configured cache size of {} for {} partition",
new Integer( cacheSize ), config.getName() );
}
}
// Now, create the entry cache for this partition
recMan = new CacheRecordManager( base, new MRU( cacheSize ) );
}
catch ( IOException e )
{
NamingException ne = new NamingException( "Could not initialize RecordManager" );
ne.setRootCause( e );
throw ne;
}
// Create the master table (the table wcontaining all the entries)
master = new JdbmMasterTable( recMan );
// Initializes this partition indices
indices = new HashMap<String,Index>();
sysIndices = new HashMap<String,Index>();
initIndices2( config.getIndexedAttributes() );
initSuffixEntry3( config.getSuffixDn(), config.getContextEntry() );
// We are done !
initialized = true;
}
/**
* Close the parttion : we have to close all the indices and the master table.
*
*/
public synchronized void destroy()
{
if ( !initialized )
{
return;
}
List<Index> array = new ArrayList<Index>();
array.addAll( indices.values() );
if ( null != ndnIdx )
{
array.add( ndnIdx );
}
if ( null != updnIdx )
{
array.add( updnIdx );
}
if ( null != aliasIdx )
{
array.add( aliasIdx );
}
if ( null != oneAliasIdx )
{
array.add( oneAliasIdx );
}
if ( null != subAliasIdx )
{
array.add( subAliasIdx );
}
if ( null != hierarchyIdx )
{
array.add( hierarchyIdx );
}
if ( null != existanceIdx )
{
array.add( existanceIdx );
}
for ( Index index:array )
{
try
{
index.close();
}
catch ( Throwable t )
{
log.error( "Failed to close an index.", t );
}
}
try
{
master.close();
}
catch ( Throwable t )
{
log.error( "Failed to close the master.", t );
}
try
{
recMan.close();
}
catch ( Throwable t )
{
log.error( "Failed to close the record manager", t );
}
initialized = false;
}
/**
* @return True is the partition store is initialized
*/
public boolean isInitialized()
{
return initialized;
}
/**
* This method is called when the synch thread is waking up, to write
* the modified data.
* @throws NamingException
*/
public synchronized void sync() throws NamingException
{
if ( !initialized )
{
return;
}
List<Index> array = new ArrayList<Index>();
array.addAll( indices.values() );
array.add( ndnIdx );
array.add( updnIdx );
array.add( aliasIdx );
array.add( oneAliasIdx );
array.add( subAliasIdx );
array.add( hierarchyIdx );
array.add( existanceIdx );
// Sync all user defined indices
for ( Index idx:array )
{
idx.sync();
}
master.sync();
try
{
recMan.commit();
}
catch ( Throwable t )
{
throw ( NamingException ) new NamingException( "Failed to commit changes to the record manager." )
.initCause( t );
}
}
// ------------------------------------------------------------------------
// I N D E X M E T H O D S
// ------------------------------------------------------------------------
public void addIndexOn( AttributeType spec, int cacheSize, int numDupLimit ) throws NamingException
{
Index idx = new JdbmIndex( spec, workingDirectory, cacheSize, numDupLimit );
indices.put( spec.getOid(), idx );
}
public Index getExistanceIndex()
{
return existanceIdx;
}
public void setExistanceIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException
{
if ( existanceIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
existanceIdx = new JdbmIndex( attrType, workingDirectory, cacheSize, numDupLimit );
sysIndices.put( attrType.getOid(), existanceIdx );
}
public Index getHierarchyIndex()
{
return hierarchyIdx;
}
public void setHierarchyIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException
{
if ( hierarchyIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
hierarchyIdx = new JdbmIndex( attrType, workingDirectory, cacheSize, numDupLimit );
sysIndices.put( attrType.getOid(), hierarchyIdx );
}
public Index getAliasIndex()
{
return aliasIdx;
}
public void setAliasIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException
{
if ( aliasIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
aliasIdx = new JdbmIndex( attrType, workingDirectory, cacheSize, numDupLimit );
sysIndices.put( attrType.getOid(), aliasIdx );
}
public Index getOneAliasIndex()
{
return oneAliasIdx;
}
public void setOneAliasIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException
{
if ( oneAliasIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
oneAliasIdx = new JdbmIndex( attrType, workingDirectory, cacheSize, numDupLimit );
sysIndices.put( attrType.getOid(), oneAliasIdx );
}
public Index getSubAliasIndex()
{
return subAliasIdx;
}
public void setSubAliasIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException
{
if ( subAliasIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
subAliasIdx = new JdbmIndex( attrType, workingDirectory, cacheSize, numDupLimit );
sysIndices.put( attrType.getOid(), subAliasIdx );
}
public Index getUpdnIndex()
{
return updnIdx;
}
public void setUpdnIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException
{
if ( updnIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
updnIdx = new JdbmIndex( attrType, workingDirectory, cacheSize, numDupLimit );
sysIndices.put( attrType.getOid(), updnIdx );
}
public Index getNdnIndex()
{
return ndnIdx;
}
public void setNdnIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException
{
if ( ndnIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
ndnIdx = new JdbmIndex( attrType, workingDirectory, cacheSize, numDupLimit );
sysIndices.put( attrType.getOid(), ndnIdx );
}
public Iterator getUserIndices()
{
return indices.keySet().iterator();
}
public Iterator getSystemIndices()
{
return sysIndices.keySet().iterator();
}
public boolean hasUserIndexOn( String id ) throws NamingException
{
return indices.containsKey( oidRegistry.getOid( id ) );
}
public boolean hasSystemIndexOn( String id ) throws NamingException
{
return sysIndices.containsKey( oidRegistry.getOid( id ) );
}
/**
* @see org.apache.directory.server.core.partition.impl.btree.BTreePartition#getUserIndex(String)
*/
public Index getUserIndex( String id ) throws IndexNotFoundException
{
try
{
id = oidRegistry.getOid( id );
}
catch ( NamingException e )
{
log.error( "Failed to identify OID for: " + id, e );
throw new IndexNotFoundException( "Failed to identify OID for: " + id, id, e );
}
if ( indices.containsKey( id ) )
{
return indices.get( id );
}
else
{
String name = "unknown";
try
{
name = oidRegistry.getPrimaryName( id );
}
catch ( NamingException e )
{
String msg = "Failed to resolve primary name for " + id + " in user index lookup";
log.error( msg, e );
throw new IndexNotFoundException( msg, id, e );
}
throw new IndexNotFoundException( "A user index on attribute " + id + " ("
+ name + ") does not exist!" );
}
}
/**
* @see BTreePartition#getEntryId(String)
*/
public Index getSystemIndex( String id ) throws IndexNotFoundException
{
try
{
id = oidRegistry.getOid( id );
}
catch ( NamingException e )
{
log.error( "Failed to identify OID for: " + id, e );
throw new IndexNotFoundException( "Failed to identify OID for: " + id, id, e );
}
if ( sysIndices.containsKey( id ) )
{
return sysIndices.get( id );
}
else
{
String name = "unknown";
try
{
name = oidRegistry.getPrimaryName( id );
}
catch ( NamingException e )
{
String msg = "Failed to resolve primary name for " + id + " in user index lookup";
log.error( msg, e );
throw new IndexNotFoundException( msg, id, e );
}
throw new IndexNotFoundException( "A system index on attribute " + id + " ("
+ name + ") does not exist!" );
}
}
public Long getEntryId( String dn ) throws NamingException
{
return (Long)ndnIdx.forwardLookup( dn );
}
public String getEntryDn( Long id ) throws NamingException
{
return ( String ) ndnIdx.reverseLookup( id );
}
public Long getParentId( String dn ) throws NamingException
{
Long childId = (Long)ndnIdx.forwardLookup( dn );
return ( Long ) hierarchyIdx.reverseLookup( childId );
}
public Long getParentId( Long childId ) throws NamingException
{
return ( Long ) hierarchyIdx.reverseLookup( childId );
}
public String getEntryUpdn( Long id ) throws NamingException
{
return ( String ) updnIdx.reverseLookup( id );
}
public String getEntryUpdn( String dn ) throws NamingException
{
Long id = (Long)ndnIdx.forwardLookup( dn );
return ( String ) updnIdx.reverseLookup( id );
}
public int count() throws NamingException
{
return master.count();
}
/**
* 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 NamingException if we cannot delete the indices
*/
private void dropAliasIndices( Long aliasId ) throws NamingException
{
String targetDn = ( String ) aliasIdx.reverseLookup( aliasId );
Long targetId = getEntryId( targetDn );
String aliasDn = getEntryDn( aliasId );
LdapDN ancestorDn = ( LdapDN ) new LdapDN( aliasDn ).getPrefix( 1 );
Long ancestorId = getEntryId( ancestorDn.toString() );
/*
* We cannot just drop all tuples in the one level and subtree indices
* 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( normSuffix ) )
{
ancestorDn = ( LdapDN ) ancestorDn.getPrefix( 1 );
ancestorId = getEntryId( ancestorDn.toString() );
subAliasIdx.drop( ancestorId, targetId );
}
// Drops all alias tuples pointing to the id of the alias to be deleted
aliasIdx.drop( aliasId );
}
/**
* Adds indices for an aliasEntry to be added to the database while checking
* for constrained alias constructs like alias cycles and chaining.
*
* @param aliasDn normalized distinguished name for the alias entry
* @param aliasTarget the user provided aliased entry dn as a string
* @param aliasId the id of alias entry to add
* @throws NamingException if index addition fails, of the alias is not
* allowed due to chaining or cycle formation.
*/
private void addAliasIndices( Long aliasId, LdapDN aliasDn, String aliasTarget ) throws NamingException
{
LdapDN normalizedAliasTargetDn = null; // Name value of aliasedObjectName
Long targetId = null; // Id of the aliasedObjectName
LdapDN ancestorDn = null; // Name of an alias entry relative
Long ancestorId = null; // Id of an alias entry relative
// Access aliasedObjectName, normalize it and generate the Name
normalizedAliasTargetDn = new LdapDN( aliasTarget );
normalizedAliasTargetDn.normalize( attributeTypeRegistry.getNormalizerMapping() );
/*
* Check For Cycles
*
* Before wasting time to lookup more values we check using the target
* dn to see if we have the possible formation of an alias cycle. This
* happens when the alias refers back to a target that is also a
* relative of the alias entry. For detection we test if the aliased
* entry Dn starts with the target Dn. If it does then we know the
* aliased target is a relative and we have a perspecitive cycle.
*/
if ( aliasDn.startsWith( normalizedAliasTargetDn ) )
{
if ( aliasDn.equals( normalizedAliasTargetDn ) )
{
throw new NamingException( "[36] aliasDereferencingProblem - " + "attempt to create alias to itself." );
}
throw new NamingException( "[36] aliasDereferencingProblem - "
+ "attempt to create alias with cycle to relative " + aliasTarget
+ " not allowed from descendent alias " + aliasDn );
}
/*
* Check For Aliases External To Naming Context
*
* id may be null but the alias may be to a valid entry in
* another namingContext. Such aliases are not allowed and we
* need to point it out to the user instead of saying the target
* does not exist when it potentially could outside of this upSuffix.
*/
if ( !normalizedAliasTargetDn.startsWith( normSuffix ) )
{
// Complain specifically about aliases to outside naming contexts
throw new NamingException( "[36] aliasDereferencingProblem -"
+ " the alias points to an entry outside of the " + upSuffix.getUpName()
+ " namingContext to an object whose existance cannot be" + " determined." );
}
// L O O K U P T A R G E T I D
targetId = (Long)ndnIdx.forwardLookup( normalizedAliasTargetDn.toNormName() );
/*
* Check For Target Existance
*
* We do not allow the creation of inconsistant aliases. Aliases should
* not be broken links. If the target does not exist we start screaming
*/
if ( null == targetId )
{
// Complain about target not existing
throw new NamingException( "[33] aliasProblem - "
+ "the alias when dereferenced would not name a known object "
+ "the aliasedObjectName must be set to a valid existing " + "entry." );
}
/*
* Detect Direct Alias Chain Creation
*
* Rather than resusitate the target to test if it is an alias and fail
* due to chaing creation we use the alias index to determine if the
* target is an alias. Hence if the alias we are about to create points
* to another alias as its target in the aliasedObjectName attribute,
* then we have a situation where an alias chain is being created.
* Alias chaining is not allowed so we throw and exception.
*/
if ( null != aliasIdx.reverseLookup( targetId ) )
{
// Complain about illegal alias chain
throw new NamingException( "[36] aliasDereferencingProblem -"
+ " the alias points to another alias. Alias chaining is" + " not supported by this backend." );
}
// Add the alias to the simple alias index
aliasIdx.add( normalizedAliasTargetDn.getNormName(), aliasId );
/*
* Handle One Level Scope Alias Index
*
* The first relative is special with respect to the one level alias
* index. If the target is not a sibling of the alias then we add the
* index entry maping the parent's id to the aliased target id.
*/
ancestorDn = ( LdapDN ) aliasDn.clone();
ancestorDn.remove( aliasDn.size() - 1 );
ancestorId = getEntryId( ancestorDn.toNormName() );
if ( !NamespaceTools.isSibling( normalizedAliasTargetDn, aliasDn ) )
{
oneAliasIdx.add( ancestorId, targetId );
}
/*
* Handle Sub Level Scope Alias Index
*
* Walk the list of relatives from the parents up to the upSuffix, testing
* to see if the alias' target is a descendant of the relative. If the
* alias target is not a descentant of the relative it extends the scope
* and is added to the sub tree scope alias index. The upSuffix node is
* ignored since everything is under its scope. The first loop
* iteration shall handle the parents.
*/
while ( !ancestorDn.equals( normSuffix ) && null != ancestorId )
{
if ( !NamespaceTools.isDescendant( ancestorDn, normalizedAliasTargetDn ) )
{
subAliasIdx.add( ancestorId, targetId );
}
ancestorDn.remove( ancestorDn.size() - 1 );
ancestorId = getEntryId( ancestorDn.toNormName() );
}
}
public void add( LdapDN normName, Attributes entry ) throws NamingException
{
Long id;
Long parentId = null;
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.
//
LdapDN parentDn = null;
if ( normName.equals( normSuffix ) )
{
parentId = 0L;
}
else
{
parentDn = ( LdapDN ) normName.clone();
parentDn.remove( parentDn.size() - 1 );
parentId = getEntryId( parentDn.toString() );
}
// don't keep going if we cannot find the parent Id
if ( parentId == null )
{
throw new LdapNameNotFoundException( "Id for parent '" + parentDn + "' not found!" );
}
Attribute objectClass = AttributeUtils.getAttribute( entry, OBJECT_CLASS_AT );
if ( objectClass == null )
{
String msg = "Entry " + normName.getUpName() + " contains no objectClass attribute: " + entry;
throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECT_CLASS_VIOLATION );
}
// Start adding the system indices
// Why bother doing a lookup if this is not an alias.
if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
{
Attribute aliasAttr = AttributeUtils.getAttribute( entry, ALIASED_OBJECT_NAME_AT );
addAliasIndices( id, normName, ( String ) aliasAttr.get() );
}
ndnIdx.add( normName.toNormName(), id );
updnIdx.add( normName.getUpName(), id );
hierarchyIdx.add( parentId, id );
// Now work on the user defined indices
NamingEnumeration list = entry.getIDs();
while ( list.hasMore() )
{
String attributeId = ( String ) list.next();
String attributeOid = oidRegistry.getOid( attributeId );
if ( hasUserIndexOn( attributeOid ) )
{
Index idx = 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
NamingEnumeration values = entry.get( attributeId ).getAll();
while ( values.hasMore() )
{
idx.add( values.next(), id );
}
// Adds only those attributes that are indexed
existanceIdx.add( attributeOid, id );
}
}
master.put( entry, id );
if ( isSyncOnWrite )
{
sync();
}
}
public Attributes lookup( Long id ) throws NamingException
{
return master.get( id );
}
public void delete( Long id ) throws NamingException
{
Attributes entry = lookup( id );
Long parentId = getParentId( id );
NamingEnumeration attrs = entry.getIDs();
Attribute objectClass = AttributeUtils.getAttribute( entry, OBJECT_CLASS_AT );
if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
{
dropAliasIndices( id );
}
ndnIdx.drop( id );
updnIdx.drop( id );
hierarchyIdx.drop( id );
// Remove parent's reference to entry only if entry is not the upSuffix
if ( !parentId.equals( 0L ) )
{
hierarchyIdx.drop( parentId, id );
}
while ( attrs.hasMore() )
{
String attributeId = ( ( String ) attrs.next() );
String attributeOid = oidRegistry.getOid( attributeId );
if ( hasUserIndexOn( attributeOid ) )
{
Index 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
NamingEnumeration values = entry.get( attributeId ).getAll();
while ( values.hasMore() )
{
index.drop( values.next(), id );
}
existanceIdx.drop( attributeOid, id );
}
}
master.delete( id );
if ( isSyncOnWrite )
{
sync();
}
}
public NamingEnumeration list( Long id ) throws NamingException
{
return hierarchyIdx.listIndices( id );
}
public int getChildCount( Long id ) throws NamingException
{
return hierarchyIdx.count( id );
}
public LdapDN getSuffix()
{
return normSuffix;
}
public LdapDN getUpSuffix()
{
return upSuffix;
}
public Attributes getSuffixEntry() throws NamingException
{
Long id = getEntryId( normSuffix.toNormName() );
if ( null == id )
{
return null;
}
return lookup( id );
}
public void setProperty( String propertyName, String propertyValue ) throws NamingException
{
master.setProperty( propertyName, propertyValue );
}
public String getProperty( String propertyName ) throws NamingException
{
return master.getProperty( propertyName );
}
public Attributes getIndices( Long id ) throws NamingException
{
Attributes attributes = new AttributesImpl();
// Get the distinguishedName to id mapping
attributes.put( "_nDn", getEntryDn( id ) );
attributes.put( "_upDn", getEntryUpdn( id ) );
attributes.put( "_parent", getParentId( id ) );
// Get all standard index attribute to value mappings
for ( Index index:this.indices.values() )
{
NamingEnumeration list = index.listReverseIndices( id );
while ( list.hasMore() )
{
IndexRecord rec = ( IndexRecord ) list.next();
Object val = rec.getIndexKey();
String attrId = index.getAttribute().getName();
Attribute attr = attributes.get( attrId );
if ( attr == null )
{
attr = new AttributeImpl( attrId );
}
attr.add( val );
attributes.put( attr );
}
}
// Get all existance mappings for this id creating a special key
// that looks like so 'existance[attribute]' and the value is set to id
NamingEnumeration list = existanceIdx.listReverseIndices( id );
StringBuffer val = new StringBuffer();
while ( list.hasMore() )
{
IndexRecord rec = ( IndexRecord ) list.next();
val.append( "_existance[" );
val.append( rec.getIndexKey() );
val.append( "]" );
String valStr = val.toString();
Attribute attr = attributes.get( valStr );
if ( attr == null )
{
attr = new AttributeImpl( valStr );
}
attr.add( rec.getEntryId() );
attributes.put( attr );
val.setLength( 0 );
}
// Get all parent child mappings for this entry as the parent using the
// key 'child' with many entries following it.
list = hierarchyIdx.listIndices( id );
Attribute childAttr = new AttributeImpl( "_child" );
attributes.put( childAttr );
while ( list.hasMore() )
{
IndexRecord rec = ( IndexRecord ) list.next();
childAttr.add( rec.getEntryId() );
}
return attributes;
}
/**
* Adds a set of attribute values while affecting the appropriate indices.
* 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 NamingException if index alteration or attribute addition
* fails.
*/
private void add( Long id, Attributes entry, Attribute mods ) throws NamingException
{
String modsOid = oidRegistry.getOid( mods.getID() );
if ( hasUserIndexOn( modsOid ) )
{
Index idx = getUserIndex( modsOid );
idx.add( mods, id );
// If the attr didn't exist for this id add it to existance index
if ( !existanceIdx.hasValue( modsOid, id ) )
{
existanceIdx.add( modsOid, id );
}
}
// add all the values in mods to the same attribute in the entry
AttributeType type = attributeTypeRegistry.lookup( modsOid );
Attribute entryAttrToAddTo = AttributeUtils.getAttribute( entry, type );
if ( entryAttrToAddTo == null )
{
entryAttrToAddTo = new AttributeImpl( mods.getID() );
entry.put( entryAttrToAddTo );
}
for ( int ii = 0; ii < mods.size(); ii++ )
{
entryAttrToAddTo.add( mods.get( ii ) );
}
if ( modsOid.equals( oidRegistry.getOid( SchemaConstants.ALIASED_OBJECT_NAME_AT ) ) )
{
String ndnStr = ( String ) ndnIdx.reverseLookup( id );
addAliasIndices( id, new LdapDN( ndnStr ), ( String ) mods.get() );
}
}
/**
* Completely removes the set of values for an attribute having the values
* supplied while affecting the appropriate indices. 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 NamingException if index alteration or attribute modification
* fails.
*/
private void remove( Long id, Attributes entry, Attribute mods ) throws NamingException
{
String modsOid = oidRegistry.getOid( mods.getID() );
if ( hasUserIndexOn( modsOid ) )
{
Index idx = getUserIndex( modsOid );
idx.drop( mods, id );
/*
* If no attribute values exist for this entryId in the index then
* we remove the existance index entry for the removed attribute.
*/
if ( null == idx.reverseLookup( id ) )
{
existanceIdx.drop( modsOid, id );
}
}
AttributeType attrType = attributeTypeRegistry.lookup( modsOid );
/*
* If there are no attribute values in the modifications then this
* implies the compelete 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 )
{
AttributeUtils.removeAttribute( attrType, entry );
}
else
{
Attribute entryAttr = AttributeUtils.getAttribute( entry, attrType );
NamingEnumeration values = mods.getAll();
while ( values.hasMore() )
{
entryAttr.remove( values.next() );
}
// if nothing is left just remove empty attribute
if ( entryAttr.size() == 0 )
{
entry.remove( entryAttr.getID() );
}
}
// Aliases->single valued comp/partial attr removal is not relevant here
if ( modsOid.equals( oidRegistry.getOid( SchemaConstants.ALIASED_OBJECT_NAME_AT ) ) )
{
dropAliasIndices( id );
}
}
/**
* Completely replaces the existing set of values for an attribute with the
* modified values supplied affecting the appropriate indices. 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 NamingException if index alteration or attribute modification
* fails.
*/
private void replace( Long id, Attributes entry, Attribute mods ) throws NamingException
{
String modsOid = oidRegistry.getOid( mods.getID() );
if ( hasUserIndexOn( modsOid ) )
{
Index idx = getUserIndex( modsOid );
// Drop all existing attribute value index entries and add new ones
idx.drop( id );
idx.add( mods, id );
/*
* If no attribute values exist for this entryId in the index then
* we remove the existance index entry for the removed attribute.
*/
if ( null == idx.reverseLookup( id ) )
{
existanceIdx.drop( modsOid, id );
}
}
String aliasAttributeOid = oidRegistry.getOid( SchemaConstants.ALIASED_OBJECT_NAME_AT );
if ( modsOid.equals( aliasAttributeOid ) )
{
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.getID() );
}
if ( modsOid.equals( aliasAttributeOid ) && mods.size() > 0 )
{
String ndnStr = ( String ) ndnIdx.reverseLookup( id );
addAliasIndices( id, new LdapDN( ndnStr ), ( String ) mods.get() );
}
}
public void modify( LdapDN dn, int modOp, Attributes mods ) throws NamingException
{
NamingEnumeration attrs = null;
Long id = getEntryId( dn.toString() );
Attributes entry = master.get( id );
switch ( modOp )
{
case ( DirContext.ADD_ATTRIBUTE ):
attrs = mods.getIDs();
while ( attrs.hasMore() )
{
String attrId = ( String ) attrs.next();
Attribute attr = mods.get( attrId );
add( id, entry, attr );
}
break;
case ( DirContext.REMOVE_ATTRIBUTE ):
attrs = mods.getIDs();
while ( attrs.hasMore() )
{
String attrId = ( String ) attrs.next();
Attribute attr = mods.get( attrId );
remove( id, entry, attr );
}
break;
case ( DirContext.REPLACE_ATTRIBUTE ):
attrs = mods.getIDs();
while ( attrs.hasMore() )
{
String attrId = ( String ) attrs.next();
Attribute attr = mods.get( attrId );
replace( id, entry, attr );
}
break;
default:
throw new NamingException( "Unidentified modification operation" );
}
master.put( entry, id );
if ( isSyncOnWrite )
{
sync();
}
}
public void modify( LdapDN dn, ModificationItemImpl[] mods ) throws NamingException
{
Long id = getEntryId( dn.toString() );
Attributes entry = master.get( id );
for ( int ii = 0; ii < mods.length; ii++ )
{
Attribute attrMods = mods[ii].getAttribute();
switch ( mods[ii].getModificationOp() )
{
case ( DirContext.ADD_ATTRIBUTE ):
add( id, entry, attrMods );
break;
case ( DirContext.REMOVE_ATTRIBUTE ):
remove( id, entry, attrMods );
break;
case ( DirContext.REPLACE_ATTRIBUTE ):
replace( id, entry, attrMods );
break;
default:
throw new NamingException( "Unidentified modification operation" );
}
}
master.put( entry, id );
if ( isSyncOnWrite )
{
sync();
}
}
/**
* Changes the relative distinuished 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 NamingException if there are any errors propagating the name
* changes.
*/
public void rename( LdapDN dn, String newRdn, boolean deleteOldRdn ) throws NamingException
{
String newRdnAttr = NamespaceTools.getRdnAttribute( newRdn );
String newRdnValue = NamespaceTools.getRdnValue( newRdn );
Long id = getEntryId( dn.toString() );
Attributes entry = lookup( id );
LdapDN updn = new LdapDN( getEntryUpdn( id ) );
/*
* 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 existance index shows the existance of the
* new Rdn attribute within this entry.
*/
String newRdnAttrOid = oidRegistry.getOid( newRdnAttr );
AttributeType newRdnAttrType = attributeTypeRegistry.lookup( newRdnAttrOid );
Attribute rdnAttr = AttributeUtils.getAttribute( entry, newRdnAttrType );
if ( rdnAttr == null )
{
rdnAttr = new AttributeImpl( newRdnAttr );
}
// add the new Rdn value only if it is not already present in the entry
if ( !rdnAttr.contains( newRdnValue ) )
{
rdnAttr.add( newRdnValue );
}
entry.put( rdnAttr );
if ( hasUserIndexOn( newRdnAttrOid ) )
{
Index idx = getUserIndex( newRdnAttrOid );
idx.add( newRdnValue, id );
// Make sure the altered entry shows the existance of the new attrib
if ( !existanceIdx.hasValue( newRdnAttrOid, id ) )
{
existanceIdx.add( newRdnAttrOid, 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 Rdn 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 existance index.
*/
if ( deleteOldRdn )
{
String oldRdn = updn.get( updn.size() - 1 );
String oldRdnAttr = NamespaceTools.getRdnAttribute( oldRdn );
String oldRdnAttrOid = oidRegistry.getOid( oldRdnAttr );
String oldRdnValue = NamespaceTools.getRdnValue( oldRdn );
AttributeType oldRdnAttrType = attributeTypeRegistry.lookup( oldRdnAttrOid );
AttributeUtils.getAttribute( entry, oldRdnAttrType ).remove( oldRdnValue );
if ( hasUserIndexOn( oldRdnAttrOid ) )
{
Index idx = getUserIndex( oldRdnAttrOid );
idx.drop( oldRdnValue, id );
/*
* If there is no value for id in this index due to our
* drop above we remove the oldRdnAttr from the existance idx
*/
if ( null == idx.reverseLookup( id ) )
{
existanceIdx.drop( oldRdnAttrOid, id );
}
}
}
/*
* H A N D L E D N C H A N G E
* ====================================================================
* 1) Build the new user defined distinguished name
* - clone / copy old updn
* - remove old upRdn from copy
* - add the new upRdn to the copy
* 2) Make call to recursive modifyDn method to change the names of the
* entry and its descendants
*/
LdapDN newUpdn = ( LdapDN ) updn.clone(); // copy da old updn
newUpdn.remove( newUpdn.size() - 1 ); // remove old upRdn
newUpdn.add( newUpdn.size(), newRdn ); // add da new upRdn
modifyDn( id, newUpdn, false ); // propagate dn changes
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
*
*/
/**
* 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 indices.
* @throws NamingException if something goes wrong
*/
private void modifyDn( Long id, LdapDN updn, boolean isMove ) throws NamingException
{
String aliasTarget = null;
// Now we can handle the appropriate name indices for all cases
ndnIdx.drop( id );
LdapDN normalizedDn = ( updn.isNormalized() ? updn : LdapDN.normalize( updn, attributeTypeRegistry.getNormalizerMapping() ) );
ndnIdx.add( ndnIdx.getNormalized( normalizedDn.toNormName() ), id );
updnIdx.drop( id );
updnIdx.add( updn.getUpName(), id );
/*
* Read Alias Index Tuples
*
* If this is a name change due to a move operation then the one and
* subtree indices 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 = ( String ) aliasIdx.reverseLookup( id );
if ( null != aliasTarget )
{
addAliasIndices( id, new LdapDN( getEntryDn( id ) ), aliasTarget );
}
}
NamingEnumeration children = list( id );
while ( children.hasMore() )
{
// Get the child and its id
IndexRecord rec = ( IndexRecord ) children.next();
Long childId = (Long)rec.getEntryId();
/*
* Calculate the Dn for the child's new name by copying the parents
* new name and adding the child's old upRdn to new name as its Rdn
*/
LdapDN childUpdn = ( LdapDN ) updn.clone();
LdapDN oldUpdn = new LdapDN( getEntryUpdn( childId ) );
String rdn = oldUpdn.get( oldUpdn.size() - 1 );
childUpdn.add( childUpdn.size(), rdn );
// Recursively change the names of the children below
modifyDn( childId, childUpdn, isMove );
}
}
public void move( LdapDN oldChildDn, LdapDN newParentDn, String newRdn, boolean deleteOldRdn ) throws NamingException
{
Long childId = getEntryId( oldChildDn.toString() );
rename( oldChildDn, newRdn, deleteOldRdn );
move( oldChildDn, childId, newParentDn );
if ( isSyncOnWrite )
{
sync();
}
}
public void move( LdapDN oldChildDn, LdapDN newParentDn ) throws NamingException
{
Long childId = getEntryId( oldChildDn.toString() );
move( oldChildDn, childId, newParentDn );
if ( isSyncOnWrite )
{
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 oldChildDn the normalized dn of the child to be moved
* @param newParentDn the normalized dn of the new parent for the child
* @throws NamingException if something goes wrong
*/
private void move( LdapDN oldChildDn, Long childId, LdapDN newParentDn ) throws NamingException
{
// Get the child and the new parent to be entries and Ids
Long newParentId = getEntryId( newParentDn.toString() );
Long oldParentId = getParentId( childId );
/*
* All aliases including and below oldChildDn, will be affected by
* the move operation with respect to one and subtree indices 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
*/
hierarchyIdx.drop( oldParentId, childId );
hierarchyIdx.add( newParentId, childId );
/*
* 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.
*/
LdapDN childUpdn = new LdapDN( getEntryUpdn( childId ) );
String childRdn = childUpdn.get( childUpdn.size() - 1 );
LdapDN newUpdn = new LdapDN( getEntryUpdn( newParentId ) );
newUpdn.add( newUpdn.size(), childRdn );
// Call the modifyDn operation with the new updn
modifyDn( childId, newUpdn, true );
}
/**
* 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 NamingException if system indices fail
*/
private void dropMovedAliasIndices( final LdapDN movedBase ) throws NamingException
{
// Find all the aliases from movedBase down
IndexAssertion isBaseDescendant = new IndexAssertion()
{
public boolean assertCandidate( IndexRecord rec ) throws NamingException
{
String dn = getEntryDn( (Long)rec.getEntryId() );
if ( dn.endsWith( movedBase.toString() ) )
{
return true;
}
return false;
}
};
Long movedBaseId = getEntryId( movedBase.toString() );
if ( aliasIdx.reverseLookup( movedBaseId ) != null )
{
dropAliasIndices( movedBaseId, movedBase );
}
NamingEnumeration aliases = new IndexAssertionEnumeration( aliasIdx.listIndices( movedBase.toString(), true ),
isBaseDescendant );
while ( aliases.hasMore() )
{
IndexRecord entry = ( IndexRecord ) aliases.next();
dropAliasIndices( (Long)entry.getEntryId(), 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 NamingException if indices fail
*/
private void dropAliasIndices( Long aliasId, LdapDN movedBase ) throws NamingException
{
String targetDn = ( String ) aliasIdx.reverseLookup( aliasId );
Long targetId = getEntryId( targetDn );
String 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.
*/
LdapDN ancestorDn = ( LdapDN ) movedBase.getPrefix( 1 );
Long ancestorId = getEntryId( ancestorDn.toString() );
/*
* We cannot just drop all tuples in the one level and subtree indices
* 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.toString() ) )
{
oneAliasIdx.drop( ancestorId, targetId );
}
subAliasIdx.drop( ancestorId, targetId );
while ( !ancestorDn.equals( upSuffix ) )
{
ancestorDn = ( LdapDN ) ancestorDn.getPrefix( 1 );
ancestorId = getEntryId( ancestorDn.toString() );
subAliasIdx.drop( ancestorId, targetId );
}
}
public void initRegistries( Registries registries )
{
this.attributeTypeRegistry = registries.getAttributeTypeRegistry();
this.oidRegistry = registries.getOidRegistry();
}
}