blob: 7cdcdcf5998c07bcccdf905ffeaff23bbe532b86 [file] [log] [blame]
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.ldap.server.partition.impl.btree.jdbm;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import jdbm.RecordManager;
import jdbm.helper.MRU;
import jdbm.recman.BaseRecordManager;
import jdbm.recman.CacheRecordManager;
import org.apache.ldap.common.MultiException;
import org.apache.ldap.common.exception.LdapNameNotFoundException;
import org.apache.ldap.common.exception.LdapSchemaViolationException;
import org.apache.ldap.common.message.LockableAttributesImpl;
import org.apache.ldap.common.message.ResultCodeEnum;
import org.apache.ldap.common.name.LdapName;
import org.apache.ldap.common.schema.AttributeType;
import org.apache.ldap.common.schema.Normalizer;
import org.apache.ldap.common.util.NamespaceTools;
import org.apache.ldap.server.configuration.ContextPartitionConfiguration;
import org.apache.ldap.server.jndi.ContextFactoryConfiguration;
import org.apache.ldap.server.partition.ContextPartition;
import org.apache.ldap.server.partition.impl.btree.BTreeContextPartition;
import org.apache.ldap.server.partition.impl.btree.Index;
import org.apache.ldap.server.partition.impl.btree.IndexAssertion;
import org.apache.ldap.server.partition.impl.btree.IndexAssertionEnumeration;
import org.apache.ldap.server.partition.impl.btree.IndexNotFoundException;
import org.apache.ldap.server.partition.impl.btree.IndexRecord;
/**
* A {@link ContextPartition} that stores entries in
* <a href="http://jdbm.sourceforge.net/">JDBM</a> database.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
public class JdbmContextPartition extends BTreeContextPartition
{
/** the JDBM record manager used by this database */
private RecordManager recMan;
/** the user provided suffix of this backend database */
private Name upSuffix;
/** the normalized suffix of this backend database */
private Name normSuffix;
/** 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 indices;
/** a map of index names to system indices */
private Map sysIndices;
/** true if open */
private boolean initialized;
/** 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;
// ------------------------------------------------------------------------
// C O N S T R U C T O R S
// ------------------------------------------------------------------------
/**
* Creates a store based on JDBM B+Trees.
*/
public JdbmContextPartition()
{
}
public synchronized void init( ContextFactoryConfiguration factoryCfg, ContextPartitionConfiguration cfg ) throws NamingException
{
this.upSuffix = new LdapName( cfg.getSuffix() );
this.normSuffix = cfg.getNormalizedSuffix( factoryCfg.getGlobalRegistries().getMatchingRuleRegistry() );
File workingDirectory = new File(
factoryCfg.getStartupConfiguration().getWorkingDirectory().getPath() +
File.separator + cfg.getName() );
workingDirectory.mkdirs();
this.workingDirectory = workingDirectory;
try
{
String path = workingDirectory.getPath() + File.separator + "master";
BaseRecordManager base = new BaseRecordManager( path );
base.disableTransactions();
recMan = new CacheRecordManager( base, new MRU( 1000 ) );
}
catch ( IOException e )
{
NamingException ne = new NamingException(
"Could not initialize RecordManager" );
ne.setRootCause( e );
throw ne;
}
master = new JdbmMasterTable( recMan );
indices = new HashMap();
sysIndices = new HashMap();
super.init( factoryCfg, cfg );
initialized = true;
}
public synchronized void destroy()
{
if ( !initialized )
{
return;
}
ArrayList array = new ArrayList();
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 );
}
Iterator list = array.iterator();
MultiException rootCause = null;
while ( list.hasNext() )
{
Index index = ( Index ) list.next();
try
{
index.close();
}
catch ( Throwable t )
{
if ( null == rootCause )
{
rootCause = new MultiException();
}
rootCause.addThrowable( t );
}
}
try
{
master.close();
}
catch ( Throwable t )
{
if ( null == rootCause )
{
rootCause = new MultiException();
}
rootCause.addThrowable( t );
}
try
{
recMan.close();
}
catch ( Throwable t )
{
if ( null == rootCause )
{
rootCause = new MultiException();
}
rootCause.addThrowable( t );
}
initialized = false;
if ( null != rootCause )
{
NamingException ne = new NamingException( "Failed to close all" );
ne.setRootCause( rootCause );
ne.printStackTrace();
}
}
public boolean isInitialized()
{
return initialized;
}
public synchronized void sync() throws NamingException
{
if( !initialized )
{
return;
}
ArrayList array = new ArrayList();
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 );
Iterator list = array.iterator();
MultiException rootCause = null;
// Sync all user defined indices
while ( list.hasNext() )
{
Index idx = ( Index ) list.next();
try
{
idx.sync();
}
catch ( Throwable t )
{
t.printStackTrace();
if ( null == rootCause )
{
rootCause = new MultiException();
}
rootCause.addThrowable( t );
}
}
try
{
master.sync();
recMan.commit();
}
catch ( Throwable t )
{
t.printStackTrace();
if ( null == rootCause )
{
rootCause = new MultiException();
}
rootCause.addThrowable( t );
}
if ( null != rootCause )
{
NamingException ne = new NamingException( "Failed to sync all" );
ne.setRootCause( rootCause );
throw ne;
}
}
// ------------------------------------------------------------------------
// I N D E X M E T H O D S
// ------------------------------------------------------------------------
public void addIndexOn( AttributeType spec ) throws NamingException
{
Index idx = new JdbmIndex( spec, workingDirectory );
indices.put( spec.getName().toLowerCase(), idx );
}
public Index getExistanceIndex()
{
return existanceIdx;
}
public void setExistanceIndexOn( AttributeType attrType ) throws NamingException
{
if ( existanceIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
existanceIdx = new JdbmIndex( attrType, workingDirectory );
sysIndices.put( attrType.getName().toLowerCase(), existanceIdx );
}
public Index getHierarchyIndex()
{
return hierarchyIdx;
}
public void setHierarchyIndexOn( AttributeType attrType ) throws NamingException
{
if ( hierarchyIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
hierarchyIdx = new JdbmIndex( attrType, workingDirectory );
sysIndices.put( attrType.getName().toLowerCase(), hierarchyIdx );
}
public Index getAliasIndex()
{
return aliasIdx;
}
public void setAliasIndexOn( AttributeType attrType ) throws NamingException
{
if ( aliasIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
aliasIdx = new JdbmIndex( attrType, workingDirectory );
sysIndices.put( attrType.getName().toLowerCase(), aliasIdx );
}
public Index getOneAliasIndex()
{
return oneAliasIdx;
}
public void setOneAliasIndexOn( AttributeType attrType ) throws NamingException
{
if ( oneAliasIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
oneAliasIdx = new JdbmIndex( attrType, workingDirectory );
sysIndices.put( attrType.getName().toLowerCase(), oneAliasIdx );
}
public Index getSubAliasIndex()
{
return subAliasIdx;
}
public void setSubAliasIndexOn( AttributeType attrType ) throws NamingException
{
if ( subAliasIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
subAliasIdx = new JdbmIndex( attrType, workingDirectory );
sysIndices.put( attrType.getName().toLowerCase(), subAliasIdx );
}
public Index getUpdnIndex()
{
return updnIdx;
}
public void setUpdnIndexOn( AttributeType attrType ) throws NamingException
{
if ( updnIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
updnIdx = new JdbmIndex( attrType, workingDirectory );
sysIndices.put( attrType.getName().toLowerCase(), updnIdx );
}
public Index getNdnIndex()
{
return ndnIdx;
}
public void setNdnIndexOn( AttributeType attrType ) throws NamingException
{
if ( ndnIdx != null )
{
NamingException e = new NamingException( "Index already set!" );
throw e;
}
ndnIdx = new JdbmIndex( attrType, workingDirectory );
sysIndices.put( attrType.getName().toLowerCase(), ndnIdx );
}
public Iterator getUserIndices()
{
return indices.keySet().iterator();
}
public Iterator getSystemIndices()
{
return sysIndices.keySet().iterator();
}
public boolean hasUserIndexOn( String attribute )
{
return indices.containsKey( attribute ) ||
indices.containsKey( attribute.toLowerCase() );
}
public boolean hasSystemIndexOn( String attribute )
{
return sysIndices.containsKey( attribute ) ||
sysIndices.containsKey( attribute.toLowerCase() );
}
/**
* @todo replace lookups to use the OID instead of the name. Also note
* that the OID registry can be used to go between names and oids.
*
* @see org.apache.ldap.server.partition.impl.btree.BTreeContextPartition#getUserIndex(String)
*/
public Index getUserIndex( String attribute ) throws IndexNotFoundException
{
String lowerCased = attribute.toLowerCase();
if ( indices.containsKey( attribute ) )
{
return ( Index ) indices.get( attribute );
}
else if ( indices.containsKey( lowerCased ) )
{
return ( Index ) indices.get( lowerCased );
}
else
{
throw new IndexNotFoundException( "An index on attribute " +
attribute + " does not exist!" );
}
}
/**
* @todo replace lookups to use the OID instead of the name. Also note
* that the OID registry can be used to go between names and oids.
*
* @see BTreeContextPartition#getEntryId(String)
*/
public Index getSystemIndex( String indexName ) throws IndexNotFoundException
{
String lowerCased = indexName.toLowerCase();
if ( sysIndices.containsKey( indexName ) )
{
return ( Index ) sysIndices.get( indexName );
}
else if ( sysIndices.containsKey( lowerCased ) )
{
return ( Index ) sysIndices.get( lowerCased );
}
else
{
throw new IndexNotFoundException( "A system index by the name of " +
indexName + " does not exist!" );
}
}
public BigInteger getEntryId( String dn ) throws NamingException
{
return ndnIdx.forwardLookup( dn );
}
public String getEntryDn( BigInteger id ) throws NamingException
{
return ( String ) ndnIdx.reverseLookup( id );
}
public BigInteger getParentId( String dn ) throws NamingException
{
BigInteger childId = ndnIdx.forwardLookup( dn );
return ( BigInteger ) hierarchyIdx.reverseLookup( childId );
}
public BigInteger getParentId( BigInteger childId ) throws NamingException
{
return ( BigInteger ) hierarchyIdx.reverseLookup( childId );
}
public String getEntryUpdn( BigInteger id ) throws NamingException
{
return ( String ) updnIdx.reverseLookup( id );
}
public String getEntryUpdn( String dn ) throws NamingException
{
BigInteger id = 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( BigInteger aliasId ) throws NamingException
{
String targetDn = ( String ) aliasIdx.reverseLookup( aliasId );
BigInteger targetId = getEntryId( targetDn );
String aliasDn = getEntryDn( aliasId );
Name ancestorDn = new LdapName( aliasDn ).getSuffix( 1 );
BigInteger 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( upSuffix ) )
{
ancestorDn = ancestorDn.getSuffix( 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( BigInteger aliasId, Name aliasDn,
String aliasTarget ) throws NamingException
{
Name targetDn = null; // Name value of aliasedObjectName
BigInteger targetId = null; // Id of the aliasedObjectName
Normalizer normalizer = null; // Temporary handle for Dn's
Name ancestorDn = null; // Name of an alias entry relative
BigInteger ancestorId = null; // Id of an alias entry relative
// Access aliasedObjectName, normalize it and generate the Name
normalizer = oneAliasIdx.getAttribute().getEquality().getNormalizer();
targetDn = new LdapName( ( String ) normalizer.normalize( aliasTarget ) );
/*
* 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( targetDn ) )
{
if ( aliasDn.equals( targetDn ) )
{
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 ( ! targetDn.startsWith( upSuffix ) )
{
// Complain specifically about aliases to outside naming contexts
throw new NamingException( "[36] aliasDereferencingProblem -"
+ " the alias points to an entry outside of the " + upSuffix
+ " namingContext to an object whose existance cannot be"
+ " determined." );
}
// L O O K U P T A R G E T I D
targetId = ndnIdx.forwardLookup( targetDn.toString() );
/*
* 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( aliasTarget, 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 = aliasDn.getSuffix( 1 );
ancestorId = getEntryId( ancestorDn.toString() );
if ( ! NamespaceTools.isSibling( targetDn, 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( upSuffix ) && null != ancestorId )
{
if ( ! NamespaceTools.isDescendant( ancestorDn, targetDn ) )
{
subAliasIdx.add( ancestorId, targetId );
}
ancestorDn = ancestorDn.getSuffix( 1 );
ancestorId = getEntryId( ancestorDn.toString() );
}
}
public void add( String updn, Name dn, Attributes entry ) throws NamingException
{
BigInteger id;
BigInteger 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.
//
if ( dn.equals( normSuffix ) )
{
parentId = BigInteger.ZERO;
}
else
{
parentId = getEntryId( dn.getSuffix( 1 ).toString() );
}
// don't keep going if we cannot find the parent Id
if ( parentId == null )
{
throw new LdapNameNotFoundException( "Id for parent '" + dn.getSuffix( 1 ).toString() + "' not found!" );
}
Attribute objectClass = entry.get( "objectClass" );
if ( objectClass == null )
{
String msg = "Entry " + updn + " contains no objectClass attribute: " + entry;
throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSVIOLATION );
}
// Start adding the system indices
// Why bother doing a lookup if this is not an alias.
if ( entry.get( "objectClass" ).contains( ContextPartition.ALIAS_OBJECT ) )
{
addAliasIndices( id, dn, ( String ) entry.get( ContextPartition.ALIAS_ATTRIBUTE ).get() );
}
ndnIdx.add( dn.toString(), id );
updnIdx.add( updn, id );
hierarchyIdx.add( parentId, id );
// Now work on the user defined indices
NamingEnumeration list = entry.getIDs();
while ( list.hasMore() )
{
String attribute = ( String ) list.next();
if ( hasUserIndexOn( attribute ) )
{
Index idx = getUserIndex( attribute );
NamingEnumeration values = entry.get( attribute ).getAll();
while ( values.hasMore() )
{
idx.add( values.next(), id );
}
// Adds only those attributes that are indexed
existanceIdx.add( attribute.toLowerCase(), id );
}
}
master.put( entry, id );
}
public Attributes lookup( BigInteger id ) throws NamingException
{
return master.get( id );
}
public void delete( BigInteger id ) throws NamingException
{
Attributes entry = lookup( id );
BigInteger parentId = getParentId( id );
NamingEnumeration attrs = entry.getIDs();
if ( entry.get( "objectClass" ).contains( ContextPartition.ALIAS_OBJECT ) )
{
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( BigInteger.ZERO ) )
{
hierarchyIdx.drop( parentId, id );
}
while ( attrs.hasMore() )
{
String attr = ( ( String ) attrs.next() );
if ( hasUserIndexOn( attr ) )
{
Index index = getUserIndex( attr );
NamingEnumeration values = entry.get( attr ).getAll();
while ( values.hasMore() )
{
index.drop( values.next(), id );
}
existanceIdx.drop( attr.toLowerCase(), id );
}
}
master.delete( id );
}
public NamingEnumeration list( BigInteger id ) throws NamingException
{
return hierarchyIdx.listIndices( id );
}
public int getChildCount( BigInteger id ) throws NamingException
{
return hierarchyIdx.count( id );
}
public Name getSuffix( boolean normalized )
{
if ( normalized )
{
return normSuffix;
}
return upSuffix;
}
public Attributes getSuffixEntry() throws NamingException
{
BigInteger id = getEntryId( upSuffix.toString() );
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( BigInteger id ) throws NamingException
{
Attributes attributes = new LockableAttributesImpl();
// 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
Iterator idxList = this.indices.values().iterator();
while ( idxList.hasNext() )
{
Index index = ( Index ) idxList.next();
NamingEnumeration list = index.listReverseIndices( id );
while ( list.hasMore() )
{
IndexRecord rec = ( IndexRecord ) list.next();
Object val = rec.getIndexKey();
attributes.put( index.getAttribute().getName(), val );
}
}
// 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( "]" );
attributes.put( val.toString(), rec.getEntryId() );
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 );
while ( list.hasMore() )
{
IndexRecord rec = ( IndexRecord ) list.next();
attributes.put( "_child", 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( BigInteger id, Attributes entry, Attribute mods )
throws NamingException
{
if ( hasUserIndexOn( mods.getID() ) )
{
Index idx = getUserIndex( mods.getID() );
idx.add( mods, id );
// If the attr didn't exist for this id add it to existance index
if ( ! existanceIdx.hasValue( mods.getID(), id ) )
{
idx.add( mods.getID(), id );
}
}
entry.put( mods );
if ( mods.getID().equals( ContextPartition.ALIAS_ATTRIBUTE ) )
{
String ndnStr = ( String ) ndnIdx.reverseLookup( id );
addAliasIndices( id, new LdapName( 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( BigInteger id, Attributes entry, Attribute mods )
throws NamingException
{
if ( hasUserIndexOn( mods.getID() ) )
{
Index idx = getUserIndex( mods.getID() );
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( mods.getID(), id );
}
}
/*
* 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 )
{
entry.remove( mods.getID() );
}
else
{
Attribute entryAttr = entry.get( mods.getID() );
NamingEnumeration values = mods.getAll();
while ( values.hasMore() )
{
entryAttr.remove( values.next() );
}
}
// Aliases->single valued comp/partial attr removal is not relevant here
if ( mods.getID().equals( ContextPartition.ALIAS_ATTRIBUTE ) )
{
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( BigInteger id, Attributes entry, Attribute mods )
throws NamingException
{
if ( hasUserIndexOn( mods.getID() ) )
{
Index idx = getUserIndex( mods.getID() );
// 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( mods.getID(), id );
}
}
if ( mods.getID().equals( ContextPartition.ALIAS_ATTRIBUTE ) )
{
dropAliasIndices( id );
}
// Automatically replaces old attributes with new modified ones
entry.put( mods );
if ( mods.getID().equals( ContextPartition.ALIAS_ATTRIBUTE ) )
{
String ndnStr = ( String ) ndnIdx.reverseLookup( id );
addAliasIndices( id, new LdapName( ndnStr ),
( String ) mods.get() );
}
}
public void modify( Name dn, int modOp, Attributes mods ) throws NamingException
{
NamingEnumeration attrs = null;
BigInteger 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 );
}
public void modify( Name dn, ModificationItem [] mods ) throws NamingException
{
BigInteger 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 );
}
/**
* 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 modifyRn( Name dn, String newRdn, boolean deleteOldRdn )
throws NamingException
{
String newRdnAttr = NamespaceTools.getRdnAttribute( newRdn );
String newRdnValue = NamespaceTools.getRdnValue( newRdn );
BigInteger id = getEntryId( dn.toString() );
Attributes entry = lookup( id );
Name updn = new LdapName( 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.
*/
entry.put( newRdnAttr, newRdnValue );
if ( hasUserIndexOn( newRdnAttr ) )
{
Index idx = getUserIndex( newRdnAttr );
idx.add( newRdnValue, id );
// Make sure the altered entry shows the existance of the new attrib
if ( ! existanceIdx.hasValue( newRdnAttr, id ) )
{
existanceIdx.add( newRdnAttr, 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 oldRdnValue = NamespaceTools.getRdnValue( oldRdn );
entry.get( oldRdnAttr ).remove( oldRdnValue );
if ( hasUserIndexOn( oldRdnAttr ) )
{
Index idx = getUserIndex( oldRdnAttr );
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( oldRdnAttr, 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
*/
Name newUpdn = ( Name ) 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
}
/*
* 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( BigInteger id, Name updn, boolean isMove )
throws NamingException
{
String aliasTarget = null;
// Now we can handle the appropriate name indices for all cases
ndnIdx.drop( id );
ndnIdx.add( ndnIdx.getNormalized( updn.toString() ), id );
updnIdx.drop( id );
updnIdx.add( updn.toString(), 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 LdapName( getEntryDn( id ) ),
aliasTarget );
}
}
NamingEnumeration children = list( id );
while ( children.hasMore() )
{
// Get the child and its id
IndexRecord rec = ( IndexRecord ) children.next();
BigInteger childId = 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
*/
Name childUpdn = ( Name ) updn.clone();
Name oldUpdn = new LdapName( getEntryUpdn( childId ) );
String rdn = LdapName.getRdn( oldUpdn );
childUpdn.add( childUpdn.size(), rdn );
// Recursively change the names of the children below
modifyDn( childId, childUpdn, isMove );
}
}
public void move( Name oldChildDn, Name newParentDn, String newRdn,
boolean deleteOldRdn ) throws NamingException
{
modifyRn( oldChildDn, newRdn, deleteOldRdn );
move( oldChildDn, newParentDn );
}
/**
* 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
*/
public void move( Name oldChildDn, Name newParentDn ) throws NamingException
{
// Get the child and the new parent to be entries and Ids
BigInteger childId = getEntryId( oldChildDn.toString() );
BigInteger newParentId = getEntryId( newParentDn.toString() );
BigInteger 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.
*/
Name childUpdn = new LdapName( getEntryUpdn( childId ) );
String childRdn = childUpdn.get( childUpdn.size() - 1 );
Name newUpdn = new LdapName( 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 Name movedBase ) throws NamingException
{
// Find all the aliases from movedBase down
IndexAssertion isBaseDescendant = new IndexAssertion()
{
public boolean assertCandidate( IndexRecord rec )
throws NamingException
{
String dn = getEntryDn( rec.getEntryId() );
if ( dn.endsWith( movedBase.toString() ) )
{
return true;
}
return false;
}
};
BigInteger 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( 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( BigInteger aliasId, Name movedBase )
throws NamingException
{
String targetDn = ( String ) aliasIdx.reverseLookup( aliasId );
BigInteger 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.
*/
Name ancestorDn = movedBase.getSuffix( 1 );
BigInteger 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 = ancestorDn.getSuffix( 1 );
ancestorId = getEntryId( ancestorDn.toString() );
subAliasIdx.drop( ancestorId, targetId );
}
}
}