blob: 952a1d279fb8623d483a61e8796ad2070cb44861 [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.xdbm;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.directory.server.constants.ApacheSchemaConstants;
import org.apache.directory.server.core.entry.ClonedServerEntry;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.shared.asn1.util.OID;
import org.apache.directory.shared.ldap.model.constants.SchemaConstants;
import org.apache.directory.shared.ldap.model.cursor.Cursor;
import org.apache.directory.shared.ldap.model.entry.Entry;
import org.apache.directory.shared.ldap.model.entry.EntryAttribute;
import org.apache.directory.shared.ldap.model.entry.Modification;
import org.apache.directory.shared.ldap.model.entry.ModificationOperation;
import org.apache.directory.shared.ldap.model.entry.Value;
import org.apache.directory.shared.ldap.model.exception.LdapAliasDereferencingException;
import org.apache.directory.shared.ldap.model.exception.LdapAliasException;
import org.apache.directory.shared.ldap.model.exception.LdapEntryAlreadyExistsException;
import org.apache.directory.shared.ldap.model.exception.LdapException;
import org.apache.directory.shared.ldap.model.exception.LdapNoSuchObjectException;
import org.apache.directory.shared.ldap.model.exception.LdapSchemaViolationException;
import org.apache.directory.shared.ldap.model.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.model.name.Ava;
import org.apache.directory.shared.ldap.model.name.Dn;
import org.apache.directory.shared.ldap.model.name.Rdn;
import org.apache.directory.shared.ldap.model.schema.MutableAttributeTypeImpl;
import org.apache.directory.shared.ldap.model.schema.MatchingRule;
import org.apache.directory.shared.ldap.model.schema.SchemaManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base implementation of a {@link Store}.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public abstract class AbstractStore<E, ID extends Comparable<ID>> implements Store<E, ID>
{
/** static logger */
private static final Logger LOG = LoggerFactory.getLogger( AbstractStore.class );
/** The default cache size is set to 10 000 objects */
public static final int DEFAULT_CACHE_SIZE = 10000;
/** Cached attributes types to avoid lookup all over the code */
protected MutableAttributeTypeImpl OBJECT_CLASS_AT;
protected MutableAttributeTypeImpl ENTRY_CSN_AT;
protected MutableAttributeTypeImpl ENTRY_UUID_AT;
protected MutableAttributeTypeImpl ALIASED_OBJECT_NAME_AT;
/** true if initialized */
protected boolean initialized;
/** the partition path to use for files */
protected URI partitionPath;
/** true if we sync disks on every write operation */
protected AtomicBoolean isSyncOnWrite = new AtomicBoolean( true );
/** The store cache size */
protected int cacheSize = DEFAULT_CACHE_SIZE;
/** The store unique identifier */
protected String id;
/** The suffix Dn */
protected Dn suffixDn;
/** A pointer on the schemaManager */
protected SchemaManager schemaManager;
/** the master table storing entries by primary key */
protected MasterTable<ID, Entry> master;
/** a map of attributeType numeric ID to user userIndices */
protected Map<String, Index<?, E, ID>> userIndices = new HashMap<String, Index<?, E, ID>>();
/** a map of attributeType numeric ID to system userIndices */
protected Map<String, Index<?, E, ID>> systemIndices = new HashMap<String, Index<?, E, ID>>();
/** the attribute presence index */
protected Index<String, E, ID> presenceIdx;
/** a system index on the entries of descendants of root Dn*/
protected Index<ID, E, ID> subLevelIdx;
/** the parent child relationship index */
protected Index<ID, E, ID> oneLevelIdx;
/** a system index on aliasedObjectName attribute */
protected Index<String, E, ID> aliasIdx;
/** the one level scope alias index */
protected Index<ID, E, ID> oneAliasIdx;
/** the subtree scope alias index */
protected Index<ID, E, ID> subAliasIdx;
/** a system index on objectClass attribute*/
protected Index<String, E, ID> objectClassIdx;
/** a system index on entryUUID attribute */
protected Index<String, E, ID> entryUuidIdx;
/** a system index on entryCSN attribute */
protected Index<String, E, ID> entryCsnIdx;
/** the relative distinguished name index */
protected Index<ParentIdAndRdn<ID>, E, ID> rdnIdx;
/**
* a flag to enable/disable hasEntry() check before adding the entry
* Note: This kind of check is already present in ExceptionInterceptor's
* add() method. This flag needs to be enabled only in cases where interceptor chain
* is not used or not yet effective at the time of adding entries into this store.
*/
private boolean checkHasEntryDuringAdd = false;
public void init( SchemaManager schemaManager ) throws Exception
{
this.schemaManager = schemaManager;
// Initialize Attribute types used all over this method
OBJECT_CLASS_AT = schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT );
ALIASED_OBJECT_NAME_AT = schemaManager.getAttributeType( SchemaConstants.ALIASED_OBJECT_NAME_AT );
ENTRY_CSN_AT = schemaManager.getAttributeType( SchemaConstants.ENTRY_CSN_AT );
ENTRY_UUID_AT = schemaManager.getAttributeType( SchemaConstants.ENTRY_UUID_AT );
}
protected void protect( String property )
{
if ( initialized )
{
throw new IllegalStateException( I18n.err( I18n.ERR_576, property ) );
}
}
public boolean isInitialized()
{
return initialized;
}
public void setPartitionPath( URI partitionPath )
{
protect( "partitionPath" );
this.partitionPath = partitionPath;
}
public URI getPartitionPath()
{
return partitionPath;
}
public void setSyncOnWrite( boolean isSyncOnWrite )
{
protect( "syncOnWrite" );
this.isSyncOnWrite.set( isSyncOnWrite );
}
public boolean isSyncOnWrite()
{
return isSyncOnWrite.get();
}
public void setCacheSize( int cacheSize )
{
protect( "cacheSize" );
this.cacheSize = cacheSize;
}
public int getCacheSize()
{
return cacheSize;
}
public void setId( String id )
{
protect( "id" );
this.id = id;
}
public String getId()
{
return id;
}
public void setSuffixDn( Dn suffixDn )
{
protect( "suffixDn" );
if ( !suffixDn.isNormalized() )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_218, suffixDn.getName() ) );
}
this.suffixDn = suffixDn;
}
public Dn getSuffixDn()
{
return suffixDn;
}
/**
* {@inheritDoc}
*/
public void setProperty( String propertyName, String propertyValue ) throws Exception
{
master.setProperty( propertyName, propertyValue );
}
/**
* {@inheritDoc}
*/
public String getProperty( String propertyName ) throws Exception
{
return master.getProperty( propertyName );
}
//------------------------------------------------------------------------
// Index handling
//------------------------------------------------------------------------
/**
* Sets up the user indices.
*/
protected void setupUserIndices() throws Exception
{
// convert and initialize system indices
Map<String, Index<?, E, ID>> tmp = new HashMap<String, Index<?, E, ID>>();
for ( String oid : userIndices.keySet() )
{
// check that the attributeType has an EQUALITY matchingRule
MutableAttributeTypeImpl attributeType = schemaManager.lookupAttributeTypeRegistry( oid );
MatchingRule mr = attributeType.getEquality();
if ( mr != null )
{
Index<?, E, ID> index = userIndices.get( oid );
index = convertAndInit( index );
tmp.put( oid, index );
}
else
{
LOG.error( I18n.err( I18n.ERR_4, attributeType.getName() ) );
}
}
userIndices = tmp;
}
/**
* Sets up the system indices.
*/
@SuppressWarnings("unchecked")
protected void setupSystemIndices() throws Exception
{
// add missing system indices
if ( getPresenceIndex() == null )
{
Index<String, E, ID> index = new GenericIndex<String, E, ID>( ApacheSchemaConstants.APACHE_EXISTENCE_AT_OID ) ;
index.setWkDirPath( partitionPath );
addIndex( index );
}
if ( getOneLevelIndex() == null )
{
Index<ID, E, ID> index = new GenericIndex<ID, E, ID>( ApacheSchemaConstants.APACHE_ONE_LEVEL_AT_OID );
index.setWkDirPath( partitionPath );
addIndex( index );
}
if ( getSubLevelIndex() == null )
{
Index<ID, E, ID> index = new GenericIndex<ID, E, ID>( ApacheSchemaConstants.APACHE_SUB_LEVEL_AT_OID );
index.setWkDirPath( partitionPath );
addIndex( index );
}
if ( getRdnIndex() == null )
{
Index<ParentIdAndRdn<ID>, E, ID> index = new GenericIndex<ParentIdAndRdn<ID>, E, ID>( ApacheSchemaConstants.APACHE_RDN_AT_OID );
index.setWkDirPath( partitionPath );
addIndex( index );
}
if ( getAliasIndex() == null )
{
Index<String, E, ID> index = new GenericIndex<String, E, ID>( ApacheSchemaConstants.APACHE_ALIAS_AT_OID );
index.setWkDirPath( partitionPath );
addIndex( index );
}
if ( getOneAliasIndex() == null )
{
Index<ID, E, ID> index = new GenericIndex<ID, E, ID>( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID );
index.setWkDirPath( partitionPath );
addIndex( index );
}
if ( getSubAliasIndex() == null )
{
Index<ID, E, ID> index = new GenericIndex<ID, E, ID>( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID );
index.setWkDirPath( partitionPath );
addIndex( index );
}
if ( getObjectClassIndex() == null )
{
Index<String, E, ID> index = new GenericIndex<String, E, ID>( SchemaConstants.OBJECT_CLASS_AT_OID );
index.setWkDirPath( partitionPath );
addIndex( index );
}
if ( getEntryUuidIndex() == null )
{
Index<String, E, ID> index = new GenericIndex<String, E, ID>( SchemaConstants.ENTRY_UUID_AT_OID );
index.setWkDirPath( partitionPath );
addIndex( index );
}
if ( getEntryCsnIndex() == null )
{
Index<String, E, ID> index = new GenericIndex<String, E, ID>( SchemaConstants.ENTRY_CSN_AT_OID );
index.setWkDirPath( partitionPath );
addIndex( index );
}
// convert and initialize system indices
for ( String oid : systemIndices.keySet() )
{
Index<?, E, ID> index = systemIndices.get( oid );
index = convertAndInit( index );
systemIndices.put( oid, index );
}
// set index shortcuts
rdnIdx = ( Index<ParentIdAndRdn<ID>, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_RDN_AT_OID );
presenceIdx = ( Index<String, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_EXISTENCE_AT_OID );
oneLevelIdx = ( Index<ID, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_ONE_LEVEL_AT_OID );
subLevelIdx = ( Index<ID, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_SUB_LEVEL_AT_OID );
aliasIdx = ( Index<String, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_ALIAS_AT_OID );
oneAliasIdx = ( Index<ID, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID );
subAliasIdx = ( Index<ID, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID );
objectClassIdx = ( Index<String, E, ID> ) systemIndices.get( SchemaConstants.OBJECT_CLASS_AT_OID );
entryUuidIdx = ( Index<String, E, ID> ) systemIndices.get( SchemaConstants.ENTRY_UUID_AT_OID );
entryCsnIdx = ( Index<String, E, ID> ) systemIndices.get( SchemaConstants.ENTRY_CSN_AT_OID );
}
/**
* Convert and initialize an index for a specific store implementation.
*
* @param index the index
* @return the converted and initialized index
* @throws Exception
*/
protected abstract Index<?, E, ID> convertAndInit( Index<?, E, ID> index ) throws Exception;
/**
* Gets the root ID of this store implementation.
*
* @return the root ID
*/
protected abstract ID getRootId();
/**
* Gets the suffix ID of this store implementation.
*
* @return the suffix ID
*/
protected ID getSuffixId() throws Exception
{
// TODO: optimize
return getEntryId( getSuffixDn() );
}
/**
* {@inheritDoc}
*/
public Iterator<String> userIndices()
{
return userIndices.keySet().iterator();
}
/**
* {@inheritDoc}
*/
public Iterator<String> systemIndices()
{
return systemIndices.keySet().iterator();
}
/**
* {@inheritDoc}
*/
public boolean hasIndexOn( String id ) throws LdapException
{
return hasUserIndexOn( id ) || hasSystemIndexOn( id );
}
/**
* {@inheritDoc}
*/
public boolean hasIndexOn( MutableAttributeTypeImpl attributeType ) throws LdapException
{
return hasUserIndexOn( attributeType ) || hasSystemIndexOn( attributeType );
}
/**
* {@inheritDoc}
*/
public boolean hasUserIndexOn( String id ) throws LdapException
{
return userIndices.containsKey( schemaManager.getAttributeTypeRegistry().getOidByName( id ) );
}
/**
* {@inheritDoc}
*/
public boolean hasUserIndexOn( MutableAttributeTypeImpl attributeType ) throws LdapException
{
return userIndices.containsKey( attributeType.getOid() );
}
/**
* {@inheritDoc}
*/
public boolean hasSystemIndexOn( String id ) throws LdapException
{
return systemIndices.containsKey( schemaManager.getAttributeTypeRegistry().getOidByName( id ) );
}
/**
* {@inheritDoc}
*/
public boolean hasSystemIndexOn( MutableAttributeTypeImpl attributeType ) throws LdapException
{
return systemIndices.containsKey( attributeType.getOid() );
}
/**
* {@inheritDoc}
*/
public Set<Index<?, E, ID>> getUserIndices()
{
return new HashSet<Index<?, E, ID>>( userIndices.values() );
}
/**
* {@inheritDoc}
*/
public Index<?, E, ID> getIndex( String id ) throws IndexNotFoundException
{
try
{
return getIndex( schemaManager.lookupAttributeTypeRegistry( id) );
}
catch ( LdapException e )
{
String msg = I18n.err( I18n.ERR_128, id );
LOG.error( msg, e );
throw new IndexNotFoundException( msg, id, e );
}
}
/**
* {@inheritDoc}
*/
public Index<?, E, ID> getIndex( MutableAttributeTypeImpl attributeType ) throws IndexNotFoundException
{
String id = attributeType.getOid();
if ( userIndices.containsKey( id ) )
{
return userIndices.get( id );
}
if ( systemIndices.containsKey( id ) )
{
return systemIndices.get( id );
}
throw new IndexNotFoundException( I18n.err( I18n.ERR_3, id, id ) );
}
/**
* {@inheritDoc}
*/
public Index<?, E, ID> getUserIndex( String id ) throws IndexNotFoundException
{
try
{
return getUserIndex( schemaManager.lookupAttributeTypeRegistry( id ) );
}
catch ( LdapException e )
{
String msg = I18n.err( I18n.ERR_128, id );
LOG.error( msg, e );
throw new IndexNotFoundException( msg, id, e );
}
}
/**
* {@inheritDoc}
*/
public Index<?, E, ID> getUserIndex( MutableAttributeTypeImpl attributeType ) throws IndexNotFoundException
{
String id = attributeType.getOid();
if ( userIndices.containsKey( id ) )
{
return userIndices.get( id );
}
throw new IndexNotFoundException( I18n.err( I18n.ERR_3, id, id ) );
}
/**
* {@inheritDoc}
*/
public Index<?, E, ID> getSystemIndex( String id ) throws IndexNotFoundException
{
try
{
return getSystemIndex( schemaManager.lookupAttributeTypeRegistry( id ) );
}
catch ( LdapException e )
{
String msg = I18n.err( I18n.ERR_128, id );
LOG.error( msg, e );
throw new IndexNotFoundException( msg, id, e );
}
}
/**
* {@inheritDoc}
*/
public Index<?, E, ID> getSystemIndex( MutableAttributeTypeImpl attributeType ) throws IndexNotFoundException
{
String id = attributeType.getOid();
if ( systemIndices.containsKey( id ) )
{
return systemIndices.get( id );
}
throw new IndexNotFoundException( I18n.err( I18n.ERR_2, id, id ) );
}
/**
* {@inheritDoc}
*/
public void addIndex( Index<?, E, ID> index ) throws Exception
{
protect( "addIndex" );
// Check that the index ID is valid
String oid = index.getAttributeId();
if ( !OID.isOID( oid ) )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_309, oid ) );
}
if ( SYS_INDEX_OIDS.contains( oid ) )
{
systemIndices.put( oid, index );
}
else
{
userIndices.put( oid, index );
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<ParentIdAndRdn<ID>, E, ID> getRdnIndex()
{
return ( Index<ParentIdAndRdn<ID>, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_RDN_AT_OID );
}
/**
* {@inheritDoc}
*/
public Index<String, E, ID> getNdnIndex()
{
return getEntryUuidIndex();
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<String, E, ID> getPresenceIndex()
{
return ( Index<String, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_EXISTENCE_AT_OID );
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<ID, E, ID> getOneLevelIndex()
{
return ( Index<ID, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_ONE_LEVEL_AT_OID );
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<ID, E, ID> getSubLevelIndex()
{
return ( Index<ID, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_SUB_LEVEL_AT_OID );
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<String, E, ID> getAliasIndex()
{
return ( Index<String, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_ALIAS_AT_OID );
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<ID, E, ID> getOneAliasIndex()
{
return ( Index<ID, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID );
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<ID, E, ID> getSubAliasIndex()
{
return ( Index<ID, E, ID> ) systemIndices.get( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID );
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<String, E, ID> getObjectClassIndex()
{
return ( Index<String, E, ID> ) systemIndices.get( SchemaConstants.OBJECT_CLASS_AT_OID );
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<String, E, ID> getEntryUuidIndex()
{
return ( Index<String, E, ID> ) systemIndices.get( SchemaConstants.ENTRY_UUID_AT_OID );
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Index<String, E, ID> getEntryCsnIndex()
{
return ( Index<String, E, ID> ) systemIndices.get( SchemaConstants.ENTRY_CSN_AT_OID );
}
/**
* {@inheritDoc}
*/
public int count() throws Exception
{
return master.count();
}
/**
* {@inheritDoc}
*/
public int getChildCount( ID id ) throws Exception
{
return oneLevelIdx.count( id );
}
//------------------------------------------------------------------------
// Dn and ID handling
//------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public ID getEntryId( Dn dn ) throws Exception
{
// Just to be sure that the Dn is normalized
if ( !dn.isNormalized() )
{
dn.normalize( schemaManager );
}
int dnSize = dn.size();
int i = suffixDn.size();
ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( getRootId(), suffixDn.getRdns() );
// Check into the Rdn index
ID curEntryId = rdnIdx.forwardLookup( key );
for ( ; i < dnSize; i++ )
{
key = new ParentIdAndRdn<ID>( curEntryId, dn.getRdn( i ) );
curEntryId = rdnIdx.forwardLookup( key );
if ( curEntryId == null )
{
break;
}
}
return curEntryId;
}
/**
* {@inheritDoc}
*/
public Dn getEntryDn( ID id ) throws Exception
{
return buildEntryDn( id );
}
/**
* {@inheritDoc}
*/
public ID getParentId( ID childId ) throws Exception
{
ParentIdAndRdn<ID> key = rdnIdx.reverseLookup( childId );
if ( key == null )
{
return null;
}
return key.getParentId();
}
//------------------------------------------------------------------------
// Operations
//------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public Entry lookup( ID id ) throws Exception
{
Entry entry = master.get( id );
if ( entry != null )
{
Dn dn = buildEntryDn( id );
entry.setDn( dn );
return entry;
}
return null;
}
/**
* {@inheritDoc}
*/
public IndexCursor<ID, E, ID> list( ID id ) throws Exception
{
IndexCursor<ID, E, ID> cursor = oneLevelIdx.forwardCursor( id );
cursor.beforeValue( id, null );
return cursor;
}
/**
* {@inheritDoc}
* TODO : We should be able to revert all the changes made to index
* if something went wrong. Also the index should auto-repair : if
* an entry does not exist in the Master table, then the index must be updated to reflect this.
*/
@SuppressWarnings("unchecked")
public synchronized void add( Entry entry ) throws Exception
{
if ( entry instanceof ClonedServerEntry )
{
throw new Exception( I18n.err( I18n.ERR_215 ) );
}
Dn entryDn = entry.getDn();
if ( checkHasEntryDuringAdd )
{
// check if the entry already exists
if ( getEntryId( entryDn ) != null )
{
LdapEntryAlreadyExistsException ne = new LdapEntryAlreadyExistsException(
I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, entryDn.getName() ) );
throw ne;
}
}
ID parentId;
ID id = master.getNextId( entry );
//
// Suffix entry cannot have a parent since it is the root so it is
// capped off using the zero value which no entry can have since
// entry sequences start at 1.
//
Dn parentDn = null;
ParentIdAndRdn<ID> key = null;
if ( entryDn.equals( suffixDn ) )
{
parentId = getRootId();
key = new ParentIdAndRdn<ID>( parentId, suffixDn.getRdns() );
}
else
{
parentDn = entryDn.getParent();
parentId = getEntryId( parentDn );
if ( parentId == null )
{
parentId = getEntryId( parentDn );
}
key = new ParentIdAndRdn<ID>( parentId, entryDn.getRdn() );
}
// don't keep going if we cannot find the parent Id
if ( parentId == null )
{
throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_216, parentDn ) );
}
rdnIdx.add( key, id );
EntryAttribute objectClass = entry.get( OBJECT_CLASS_AT );
if ( objectClass == null )
{
String msg = I18n.err( I18n.ERR_217, entryDn.getName(), entry );
ResultCodeEnum rc = ResultCodeEnum.OBJECT_CLASS_VIOLATION;
LdapSchemaViolationException e = new LdapSchemaViolationException( rc, msg );
//e.setResolvedName( entryDn );
throw e;
}
// Start adding the system userIndices
// Why bother doing a lookup if this is not an alias.
// First, the ObjectClass index
for ( Value<?> value : objectClass )
{
objectClassIdx.add( value.getString(), id );
}
if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
{
EntryAttribute aliasAttr = entry.get( ALIASED_OBJECT_NAME_AT );
addAliasIndices( id, entryDn, aliasAttr.getString() );
}
if ( !Character.isDigit( entryDn.getNormName().charAt( 0 ) ) )
{
throw new IllegalStateException( I18n.err( I18n.ERR_218, entryDn.getNormName() ) );
}
oneLevelIdx.add( parentId, id );
// Update the EntryCsn index
EntryAttribute entryCsn = entry.get( ENTRY_CSN_AT );
if ( entryCsn == null )
{
String msg = I18n.err( I18n.ERR_219, entryDn.getName(), entry );
throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg );
}
entryCsnIdx.add( entryCsn.getString(), id );
// Update the EntryUuid index
EntryAttribute entryUuid = entry.get( ENTRY_UUID_AT );
if ( entryUuid == null )
{
String msg = I18n.err( I18n.ERR_220, entryDn.getName(), entry );
throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg );
}
entryUuidIdx.add( entryUuid.getString(), id );
ID tempId = parentId;
while ( ( tempId != null ) && ( !tempId.equals( getRootId() ) ) && ( !tempId.equals( getSuffixId() ) ) )
{
subLevelIdx.add( tempId, id );
tempId = getParentId( tempId );
}
// making entry an ancestor/descendent of itself in sublevel index
subLevelIdx.add( id, id );
// Now work on the user defined userIndices
for ( EntryAttribute attribute : entry )
{
String attributeOid = attribute.getAttributeType().getOid();
if ( hasUserIndexOn( attribute.getAttributeType() ) )
{
Index<Object, E, ID> idx = ( Index<Object, E, ID> ) getUserIndex( attributeOid );
// here lookup by attributeId is OK since we got attributeId from
// the entry via the enumeration - it's in there as is for sure
for ( Value<?> value : attribute )
{
idx.add( value.get(), id );
}
// Adds only those attributes that are indexed
presenceIdx.add( attributeOid, id );
}
}
master.put( id, entry );
if ( isSyncOnWrite.get() )
{
sync();
}
}
/**
* {@inheritDoc}
*/
public synchronized void modify( Dn dn, ModificationOperation modOp, Entry mods ) throws Exception
{
if ( mods instanceof ClonedServerEntry )
{
throw new Exception( I18n.err( I18n.ERR_215 ) );
}
ID id = getEntryId( dn );
Entry entry = master.get( id );
for ( MutableAttributeTypeImpl attributeType : mods.getAttributeTypes() )
{
EntryAttribute attr = mods.get( attributeType );
switch ( modOp )
{
case ADD_ATTRIBUTE:
add( id, entry, attr );
break;
case REMOVE_ATTRIBUTE:
remove( id, entry, attr );
break;
case REPLACE_ATTRIBUTE:
replace( id, entry, attr );
break;
default:
throw new LdapException( I18n.err( I18n.ERR_221 ) );
}
}
updateCsnIndex( entry, id );
master.put( id, entry );
if ( isSyncOnWrite.get() )
{
sync();
}
}
/**
* {@inheritDoc}
*/
public synchronized Entry modify( Dn dn, List<Modification> mods ) throws Exception
{
ID id = getEntryId( dn );
Entry entry = master.get( id );
for ( Modification mod : mods )
{
EntryAttribute attrMods = mod.getAttribute();
switch ( mod.getOperation() )
{
case ADD_ATTRIBUTE:
add( id, entry, attrMods );
break;
case REMOVE_ATTRIBUTE:
remove( id, entry, attrMods );
break;
case REPLACE_ATTRIBUTE:
replace( id, entry, attrMods );
break;
default:
throw new LdapException( I18n.err( I18n.ERR_221 ) );
}
}
updateCsnIndex( entry, id );
master.put( id, entry );
if ( isSyncOnWrite.get() )
{
sync();
}
return entry;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public synchronized void delete( ID id ) throws Exception
{
Entry entry = master.get( id );
EntryAttribute objectClass = entry.get( OBJECT_CLASS_AT );
if ( objectClass.contains( SchemaConstants.ALIAS_OC ) )
{
dropAliasIndices( id );
}
for ( Value<?> value : objectClass )
{
objectClassIdx.drop( value.getString(), id );
}
rdnIdx.drop( id );
oneLevelIdx.drop( id );
subLevelIdx.drop( id );
entryCsnIdx.drop( id );
entryUuidIdx.drop( id );
for ( EntryAttribute attribute : entry )
{
String attributeOid = attribute.getAttributeType().getOid();
if ( hasUserIndexOn( attribute.getAttributeType() ) )
{
Index<?, E, ID> index = getUserIndex( attributeOid );
// here lookup by attributeId is ok since we got attributeId from
// the entry via the enumeration - it's in there as is for sure
for ( Value<?> value : attribute )
{
( ( Index ) index ).drop( value.get(), id );
}
presenceIdx.drop( attributeOid, id );
}
}
master.delete( id );
if ( isSyncOnWrite.get() )
{
sync();
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public synchronized void rename( Dn dn, Rdn newRdn, boolean deleteOldRdn, Entry entry ) throws Exception
{
ID id = getEntryId( dn );
if ( entry == null )
{
entry = lookup( id );
}
Dn updn = entry.getDn();
newRdn.normalize( schemaManager );
/*
* H A N D L E N E W R D N
* ====================================================================
* Add the new Rdn attribute to the entry. If an index exists on the
* new Rdn attribute we add the index for this attribute value pair.
* Also we make sure that the presence index shows the existence of the
* new Rdn attribute within this entry.
*/
for ( Ava newAtav : newRdn )
{
String newNormType = newAtav.getNormType();
Object newNormValue = newAtav.getNormValue().get();
MutableAttributeTypeImpl newRdnAttrType = schemaManager.lookupAttributeTypeRegistry( newNormType );
entry.add( newRdnAttrType, newAtav.getUpValue() );
if ( hasUserIndexOn( newNormType ) )
{
Index<?, E, ID> index = getUserIndex( newNormType );
( ( Index ) index ).add( newNormValue, id );
// Make sure the altered entry shows the existence of the new attrib
if ( !presenceIdx.forward( newNormType, id ) )
{
presenceIdx.add( newNormType, id );
}
}
}
/*
* H A N D L E O L D R D N
* ====================================================================
* If the old Rdn is to be removed we need to get the attribute and
* value for it. Keep in mind the old Rdn need not be based on the
* same attr as the new one. We remove the Rdn value from the entry
* and remove the value/id tuple from the index on the old Rdn attr
* if any. We also test if the delete of the old Rdn index tuple
* removed all the attribute values of the old Rdn using a reverse
* lookup. If so that means we blew away the last value of the old
* Rdn attribute. In this case we need to remove the attrName/id
* tuple from the presence index.
*
* We only remove an ATAV of the old Rdn if it is not included in the
* new Rdn.
*/
if ( deleteOldRdn )
{
Rdn oldRdn = updn.getRdn();
for ( Ava oldAtav : oldRdn )
{
// check if the new ATAV is part of the old Rdn
// if that is the case we do not remove the ATAV
boolean mustRemove = true;
for ( Ava newAtav : newRdn )
{
if ( oldAtav.equals( newAtav ) )
{
mustRemove = false;
break;
}
}
if ( mustRemove )
{
String oldNormType = oldAtav.getNormType();
String oldNormValue = oldAtav.getNormValue().getString();
MutableAttributeTypeImpl oldRdnAttrType = schemaManager.lookupAttributeTypeRegistry( oldNormType );
entry.remove( oldRdnAttrType, oldNormValue );
if ( hasUserIndexOn( oldNormType ) )
{
Index<?, E, ID> index = getUserIndex( oldNormType );
( ( Index ) index ).drop( oldNormValue, id );
/*
* If there is no value for id in this index due to our
* drop above we remove the oldRdnAttr from the presence idx
*/
if ( null == index.reverseLookup( id ) )
{
presenceIdx.drop( oldNormType, id );
}
}
}
}
}
/*
* H A N D L E D N C H A N G E
* ====================================================================
* We only need to update the Rdn index.
* No need to calculate the new Dn.
*/
ID parentId = getParentId( id );
rdnIdx.drop( id );
ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( parentId, newRdn );
rdnIdx.add( key, id );
master.put( id, entry );
if ( isSyncOnWrite.get() )
{
sync();
}
}
/**
* {@inheritDoc}
*/
public synchronized void rename( Dn dn, Rdn newRdn, boolean deleteOldRdn ) throws Exception
{
rename( dn, newRdn, deleteOldRdn, null );
}
/**
* {@inheritDoc}
*/
public synchronized void moveAndRename( Dn oldDn, Dn newSuperiorDn, Rdn newRdn, Entry modifiedEntry, boolean deleteOldRdn ) throws Exception
{
// Check that the old entry exists
ID oldId = getEntryId( oldDn );
if ( oldId == null )
{
// This is not allowed : the old entry must exist
LdapNoSuchObjectException nse = new LdapNoSuchObjectException(
I18n.err( I18n.ERR_256_NO_SUCH_OBJECT, oldDn ) );
throw nse;
}
// Check that the new superior exist
ID newSuperiorId = getEntryId( newSuperiorDn );
if ( newSuperiorId == null )
{
// This is not allowed : the new superior must exist
LdapNoSuchObjectException nse = new LdapNoSuchObjectException(
I18n.err( I18n.ERR_256_NO_SUCH_OBJECT, newSuperiorDn ) );
throw nse;
}
Dn newDn = newSuperiorDn.add( newRdn );
// Now check that the new entry does not exist
ID newId = getEntryId( newDn );
if ( newId != null )
{
// This is not allowed : we should not be able to move an entry
// to an existing position
LdapEntryAlreadyExistsException ne = new LdapEntryAlreadyExistsException(
I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, newSuperiorDn.getName() ) );
throw ne;
}
rename( oldDn, newRdn, deleteOldRdn, modifiedEntry );
moveAndRename( oldDn, oldId, newSuperiorDn, newRdn, modifiedEntry );
if ( isSyncOnWrite.get() )
{
sync();
}
}
/**
* {@inheritDoc}
*/
public synchronized void move( Dn oldDn, Dn newSuperiorDn, Dn newDn ) throws Exception
{
move( oldDn, newSuperiorDn, newDn, null );
}
/**
* {@inheritDoc}
*/
public synchronized void move( Dn oldDn, Dn newSuperiorDn, Dn newDn, Entry modifiedEntry ) throws Exception
{
// Check that the parent Dn exists
ID newParentId = getEntryId( newSuperiorDn );
if ( newParentId == null )
{
// This is not allowed : the parent must exist
LdapEntryAlreadyExistsException ne = new LdapEntryAlreadyExistsException(
I18n.err( I18n.ERR_256_NO_SUCH_OBJECT, newSuperiorDn.getName() ) );
throw ne;
}
// Now check that the new entry does not exist
ID newId = getEntryId( newDn );
if ( newId != null )
{
// This is not allowed : we should not be able to move an entry
// to an existing position
LdapEntryAlreadyExistsException ne = new LdapEntryAlreadyExistsException(
I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, newSuperiorDn.getName() ) );
throw ne;
}
// Get the entry and the old parent IDs
ID entryId = getEntryId( oldDn );
ID oldParentId = getParentId( entryId );
/*
* All aliases including and below oldChildDn, will be affected by
* the move operation with respect to one and subtree userIndices since
* their relationship to ancestors above oldChildDn will be
* destroyed. For each alias below and including oldChildDn we will
* drop the index tuples mapping ancestor ids above oldChildDn to the
* respective target ids of the aliases.
*/
dropMovedAliasIndices( oldDn );
/*
* Drop the old parent child relationship and add the new one
* Set the new parent id for the child replacing the old parent id
*/
oneLevelIdx.drop( oldParentId, entryId );
oneLevelIdx.add( newParentId, entryId );
updateSubLevelIndex( entryId, oldParentId, newParentId );
// Update the Rdn index
rdnIdx.drop( entryId );
ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( newParentId, oldDn.getRdn() );
rdnIdx.add( key, entryId );
/*
* Read Alias Index Tuples
*
* If this is a name change due to a move operation then the one and
* subtree userIndices for aliases were purged before the aliases were
* moved. Now we must add them for each alias entry we have moved.
*
* aliasTarget is used as a marker to tell us if we're moving an
* alias. If it is null then the moved entry is not an alias.
*/
String aliasTarget = aliasIdx.reverseLookup( entryId );
if ( null != aliasTarget )
{
addAliasIndices( entryId, buildEntryDn( entryId ), aliasTarget );
}
// Update the master table with the modified entry
// Warning : this test is an hack. As we may call the Store API directly
// we may not have a modified entry to update. For instance, if the ModifierName
// or ModifyTimeStamp AT are not updated, there is no reason we want to update the
// master table.
if ( modifiedEntry != null )
{
master.put( entryId, modifiedEntry );
}
if ( isSyncOnWrite.get() )
{
sync();
}
}
//------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------
/**
* builds the Dn of the entry identified by the given id
*
* @param id the entry's id
* @return the normalized Dn of the entry
* @throws Exception
*/
protected Dn buildEntryDn( ID id ) throws Exception
{
ID parentId = id;
List<Rdn> rdnList = new ArrayList<Rdn>();
StringBuilder upName = new StringBuilder();
String normName = "";
do
{
ParentIdAndRdn<ID> cur = rdnIdx.reverseLookup( parentId );
Rdn[] rdns = cur.getRdns();
for ( Rdn rdn : rdns )
{
if ( rdnList.isEmpty() )
{
normName = rdn.getNormName();
upName.append( rdn.getName() );
}
else
{
normName = normName + "," + rdn.getNormName();
upName.append( ',' ).append( rdn.getName() );
}
rdnList.add( rdn );
}
parentId = cur.getParentId();
}
while ( !parentId.equals( getRootId() ) );
Dn dn = new Dn( schemaManager, upName.toString() );
return dn;
}
/**
* Adds a set of attribute values while affecting the appropriate userIndices.
* The entry is not persisted: it is only changed in anticipation for a put
* into the master table.
*
* @param id the primary key of the entry
* @param entry the entry to alter
* @param mods the attribute and values to add
* @throws Exception if index alteration or attribute addition fails
*/
@SuppressWarnings("unchecked")
protected void add( ID id, Entry entry, EntryAttribute mods ) throws Exception
{
if ( entry instanceof ClonedServerEntry )
{
throw new Exception( I18n.err( I18n.ERR_215 ) );
}
String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
// Special case for the ObjectClass index
if ( modsOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
{
for ( Value<?> value : mods )
{
objectClassIdx.add( value.getString(), id );
}
}
else if ( hasUserIndexOn( mods.getAttributeType() ) )
{
Index<?, E, ID> index = getUserIndex( modsOid );
for ( Value<?> value : mods )
{
( ( Index ) index ).add( value.get(), id );
}
// If the attr didn't exist for this id add it to existence index
if ( !presenceIdx.forward( modsOid, id ) )
{
presenceIdx.add( modsOid, id );
}
}
// add all the values in mods to the same attribute in the entry
for ( Value<?> value : mods )
{
entry.add( mods.getAttributeType(), value );
}
if ( modsOid.equals( SchemaConstants.ALIASED_OBJECT_NAME_AT_OID ) )
{
Dn ndn = getEntryDn( id );
addAliasIndices( id, ndn, mods.getString() );
}
}
/**
* Completely replaces the existing set of values for an attribute with the
* modified values supplied affecting the appropriate userIndices. The entry
* is not persisted: it is only changed in anticipation for a put into the
* master table.
*
* @param id the primary key of the entry
* @param entry the entry to alter
* @param mods the replacement attribute and values
* @throws Exception if index alteration or attribute modification
* fails.
*/
@SuppressWarnings("unchecked")
protected void replace( ID id, Entry entry, EntryAttribute mods ) throws Exception
{
if ( entry instanceof ClonedServerEntry )
{
throw new Exception( I18n.err( I18n.ERR_215 ) );
}
String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
// Special case for the ObjectClass index
if ( mods.getAttributeType().equals( OBJECT_CLASS_AT ) )
{
// if the id exists in the index drop all existing attribute
// value index entries and add new ones
if ( objectClassIdx.reverse( id ) )
{
objectClassIdx.drop( id );
}
for ( Value<?> value : mods )
{
objectClassIdx.add( value.getString(), id );
}
}
else if ( hasUserIndexOn( mods.getAttributeType() ) )
{
Index<?, E, ID> index = getUserIndex( modsOid );
// if the id exists in the index drop all existing attribute
// value index entries and add new ones
if ( index.reverse( id ) )
{
( ( Index<?, E, ID> ) index ).drop( id );
}
for ( Value<?> value : mods )
{
( ( Index<Object, E, ID> ) index ).add( value.get(), id );
}
/*
* If no attribute values exist for this entryId in the index then
* we remove the presence index entry for the removed attribute.
*/
if ( null == index.reverseLookup( id ) )
{
presenceIdx.drop( modsOid, id );
}
}
String aliasAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName(
SchemaConstants.ALIASED_OBJECT_NAME_AT );
if ( mods.getAttributeType().equals( ALIASED_OBJECT_NAME_AT ) )
{
dropAliasIndices( id );
}
// replaces old attributes with new modified ones if they exist
if ( mods.size() > 0 )
{
entry.put( mods );
}
else
// removes old attributes if new replacements do not exist
{
entry.remove( mods );
}
if ( modsOid.equals( aliasAttributeOid ) && mods.size() > 0 )
{
Dn entryDn = getEntryDn( id );
addAliasIndices( id, entryDn, mods.getString() );
}
}
/**
* Completely removes the set of values for an attribute having the values
* supplied while affecting the appropriate userIndices. The entry is not
* persisted: it is only changed in anticipation for a put into the master
* table. Note that an empty attribute w/o values will remove all the
* values within the entry where as an attribute w/ values will remove those
* attribute values it contains.
*
* @param id the primary key of the entry
* @param entry the entry to alter
* @param mods the attribute and its values to delete
* @throws Exception if index alteration or attribute modification fails.
*/
@SuppressWarnings("unchecked")
protected void remove( ID id, Entry entry, EntryAttribute mods ) throws Exception
{
if ( entry instanceof ClonedServerEntry )
{
throw new Exception( I18n.err( I18n.ERR_215 ) );
}
String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() );
// Special case for the ObjectClass index
if ( mods.getAttributeType().equals( OBJECT_CLASS_AT ) )
{
/*
* If there are no attribute values in the modifications then this
* implies the complete removal of the attribute from the index. Else
* we remove individual tuples from the index.
*/
if ( mods.size() == 0 )
{
objectClassIdx.drop( id );
}
else
{
for ( Value<?> value : mods )
{
objectClassIdx.drop( value.getString(), id );
}
}
}
else if ( hasUserIndexOn( mods.getAttributeType() ) )
{
Index<?, E, ID> index = getUserIndex( modsOid );
/*
* If there are no attribute values in the modifications then this
* implies the complete removal of the attribute from the index. Else
* we remove individual tuples from the index.
*/
if ( mods.size() == 0 )
{
( ( Index ) index ).drop( id );
}
else
{
for ( Value<?> value : mods )
{
( ( Index ) index ).drop( value.get(), id );
}
}
/*
* If no attribute values exist for this entryId in the index then
* we remove the presence index entry for the removed attribute.
*/
if ( null == index.reverseLookup( id ) )
{
presenceIdx.drop( modsOid, id );
}
}
MutableAttributeTypeImpl attrType = schemaManager.lookupAttributeTypeRegistry( modsOid );
/*
* If there are no attribute values in the modifications then this
* implies the complete removal of the attribute from the entry. Else
* we remove individual attribute values from the entry in mods one
* at a time.
*/
if ( mods.size() == 0 )
{
entry.removeAttributes( mods.getAttributeType() );
}
else
{
EntryAttribute entryAttr = entry.get( mods.getAttributeType() );
for ( Value<?> value : mods )
{
entryAttr.remove( value );
}
// if nothing is left just remove empty attribute
if ( entryAttr.size() == 0 )
{
entry.removeAttributes( entryAttr.getId() );
}
}
// Aliases->single valued comp/partial attr removal is not relevant here
if ( mods.getAttributeType().equals( ALIASED_OBJECT_NAME_AT ) )
{
dropAliasIndices( id );
}
}
/////////////////////////////////////////////////////////
/**
* Adds userIndices 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 LdapException if index addition fails, and if the alias is
* not allowed due to chaining or cycle formation.
* @throws Exception if the wrappedCursor btrees cannot be altered
*/
protected void addAliasIndices( ID aliasId, Dn aliasDn, String aliasTarget ) throws Exception
{
Dn normalizedAliasTargetDn; // Name value of aliasedObjectName
ID targetId; // Id of the aliasedObjectName
Dn ancestorDn; // Name of an alias entry relative
ID ancestorId; // Id of an alias entry relative
// Access aliasedObjectName, normalize it and generate the Name
normalizedAliasTargetDn = new Dn( schemaManager, 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.isDescendantOf( normalizedAliasTargetDn ) )
{
if ( aliasDn.equals( normalizedAliasTargetDn ) )
{
String msg = I18n.err( I18n.ERR_223 );
LdapAliasDereferencingException e = new LdapAliasDereferencingException( msg );
//e.setResolvedName( aliasDn );
throw e;
}
String msg = I18n.err( I18n.ERR_224, aliasTarget, aliasDn );
LdapAliasDereferencingException e = new LdapAliasDereferencingException( msg );
//e.setResolvedName( aliasDn );
throw e;
}
/*
* 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.isDescendantOf( suffixDn ) )
{
String msg = I18n.err( I18n.ERR_225, suffixDn.getName() );
LdapAliasDereferencingException e = new LdapAliasDereferencingException( msg );
//e.setResolvedName( aliasDn );
throw e;
}
// L O O K U P T A R G E T I D
targetId = getEntryId( normalizedAliasTargetDn );
/*
* Check For Target Existence
*
* We do not allow the creation of inconsistent aliases. Aliases should
* not be broken links. If the target does not exist we start screaming
*/
if ( null == targetId )
{
// Complain about target not existing
String msg = I18n.err( I18n.ERR_581, aliasDn.getName(), aliasTarget );
LdapAliasException e = new LdapAliasException( msg );
//e.setResolvedName( aliasDn );
throw e;
}
/*
* 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 ) )
{
String msg = I18n.err( I18n.ERR_227 );
LdapAliasDereferencingException e = new LdapAliasDereferencingException( msg );
//e.setResolvedName( aliasDn );
throw e;
}
// 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 = aliasDn.getParent();
ancestorId = getEntryId( ancestorDn );
// check if alias parent and aliased entry are the same
Dn normalizedAliasTargetParentDn = normalizedAliasTargetDn.getParent();
if ( !aliasDn.isDescendantOf( normalizedAliasTargetParentDn ) )
{
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( suffixDn ) && null != ancestorId )
{
if ( !normalizedAliasTargetDn.isDescendantOf( ancestorDn ) )
{
subAliasIdx.add( ancestorId, targetId );
}
ancestorDn = ancestorDn.getParent();
ancestorId = getEntryId( ancestorDn );
}
}
/**
* Removes the index entries for an alias before the entry is deleted from
* the master table.
*
* @todo Optimize this by walking the hierarchy index instead of the name
* @param aliasId the id of the alias entry in the master table
* @throws LdapException if we cannot parse ldap names
* @throws Exception if we cannot delete index values in the database
*/
protected void dropAliasIndices( ID aliasId ) throws Exception
{
String targetDn = aliasIdx.reverseLookup( aliasId );
ID targetId = getEntryId( new Dn( schemaManager, targetDn ) );
if ( targetId == null )
{
// the entry doesn't exist, probably it has been deleted or renamed
// TODO: this is just a workaround for now, the alias indices should be updated when target entry is deleted or removed
return;
}
Dn aliasDn = getEntryDn( aliasId );
Dn ancestorDn = aliasDn.getParent();
ID ancestorId = getEntryId( ancestorDn );
/*
* We cannot just drop all tuples in the one level and subtree userIndices
* linking baseIds to the targetId. If more than one alias refers to
* the target then droping all tuples with a value of targetId would
* make all other aliases to the target inconsistent.
*
* We need to walk up the path of alias ancestors until we reach the
* upSuffix, deleting each ( ancestorId, targetId ) tuple in the
* subtree scope alias. We only need to do this for the direct parent
* of the alias on the one level subtree.
*/
oneAliasIdx.drop( ancestorId, targetId );
subAliasIdx.drop( ancestorId, targetId );
while ( !ancestorDn.equals( suffixDn ) && ancestorDn.size() > suffixDn.size() )
{
ancestorDn = ancestorDn.getParent();
ancestorId = getEntryId( ancestorDn );
subAliasIdx.drop( ancestorId, targetId );
}
// Drops all alias tuples pointing to the id of the alias to be deleted
aliasIdx.drop( aliasId );
}
/**
* For all aliases including and under the moved base, this method removes
* one and subtree alias index tuples for old ancestors above the moved base
* that will no longer be ancestors after the move.
*
* @param movedBase the base at which the move occured - the moved node
* @throws Exception if system userIndices fail
*/
protected void dropMovedAliasIndices( final Dn movedBase ) throws Exception
{
ID movedBaseId = getEntryId( movedBase );
if ( aliasIdx.reverseLookup( movedBaseId ) != null )
{
dropAliasIndices( movedBaseId, movedBase );
}
}
/**
* For the alias id all ancestor one and subtree alias tuples are moved
* above the moved base.
*
* @param aliasId the id of the alias
* @param movedBase the base where the move occured
* @throws Exception if userIndices fail
*/
protected void dropAliasIndices( ID aliasId, Dn movedBase ) throws Exception
{
String targetDn = aliasIdx.reverseLookup( aliasId );
ID targetId = getEntryId( new Dn( schemaManager, targetDn ) );
Dn aliasDn = getEntryDn( aliasId );
/*
* Start droping index tuples with the first ancestor right above the
* moved base. This is the first ancestor effected by the move.
*/
Dn ancestorDn = new Dn( schemaManager, movedBase.getRdn( 0 ) );
ID ancestorId = getEntryId( ancestorDn );
/*
* We cannot just drop all tuples in the one level and subtree userIndices
* linking baseIds to the targetId. If more than one alias refers to
* the target then droping all tuples with a value of targetId would
* make all other aliases to the target inconsistent.
*
* We need to walk up the path of alias ancestors right above the moved
* base until we reach the upSuffix, deleting each ( ancestorId,
* targetId ) tuple in the subtree scope alias. We only need to do
* this for the direct parent of the alias on the one level subtree if
* the moved base is the alias.
*/
if ( aliasDn.equals( movedBase ) )
{
oneAliasIdx.drop( ancestorId, targetId );
}
subAliasIdx.drop( ancestorId, targetId );
while ( !ancestorDn.equals( suffixDn ) )
{
ancestorDn = new Dn( schemaManager, ancestorDn.getRdn( 0 ) );
ancestorId = getEntryId( ancestorDn );
subAliasIdx.drop( ancestorId, targetId );
}
}
/**
* Moves an entry under a new parent. The operation causes a shift in the
* parent child relationships between the old parent, new parent and the
* child moved. All other descendant entries under the child never change
* their direct parent child relationships. Hence after the parent child
* relationship changes are broken at the old parent and set at the new
* parent a modifyDn operation is conducted to handle name changes
* propagating down through the moved child and its descendants.
*
* @param oldDn the normalized dn of the child to be moved
* @param childId the id of the child being moved
* @param newRdn the normalized dn of the new parent for the child
* @param modifiedEntry the modified entry
* @throws Exception if something goes wrong
*/
protected void moveAndRename( Dn oldDn, ID childId, Dn newSuperior, Rdn newRdn, Entry modifiedEntry ) throws Exception
{
// Get the child and the new parent to be entries and Ids
ID newParentId = getEntryId( newSuperior );
ID oldParentId = getParentId( childId );
/*
* All aliases including and below oldChildDn, will be affected by
* the move operation with respect to one and subtree userIndices since
* their relationship to ancestors above oldChildDn will be
* destroyed. For each alias below and including oldChildDn we will
* drop the index tuples mapping ancestor ids above oldChildDn to the
* respective target ids of the aliases.
*/
dropMovedAliasIndices( oldDn );
/*
* Drop the old parent child relationship and add the new one
* Set the new parent id for the child replacing the old parent id
*/
oneLevelIdx.drop( oldParentId, childId );
oneLevelIdx.add( newParentId, childId );
updateSubLevelIndex( childId, oldParentId, newParentId );
/*
* Update the Rdn index
*/
rdnIdx.drop( childId );
ParentIdAndRdn<ID> key = new ParentIdAndRdn<ID>( newParentId, newRdn );
rdnIdx.add( key, childId );
/*
* Read Alias Index Tuples
*
* If this is a name change due to a move operation then the one and
* subtree userIndices for aliases were purged before the aliases were
* moved. Now we must add them for each alias entry we have moved.
*
* aliasTarget is used as a marker to tell us if we're moving an
* alias. If it is null then the moved entry is not an alias.
*/
String aliasTarget = aliasIdx.reverseLookup( childId );
if ( null != aliasTarget )
{
addAliasIndices( childId, buildEntryDn( childId ), aliasTarget );
}
// Update the master table with the modified entry
// Warning : this test is an hack. As we may call the Store API directly
// we may not have a modified entry to update. For instance, if the ModifierName
// or ModifyTimeStamp AT are not updated, there is no reason we want to update the
// master table.
if ( modifiedEntry != null )
{
master.put( childId, modifiedEntry );
}
}
/**
* Updates the SubLevel Index as part of a move operation.
*
* @param entryId child id to be moved
* @param oldParentId old parent's id
* @param newParentId new parent's id
* @throws Exception
*/
protected void updateSubLevelIndex( ID entryId, ID oldParentId, ID newParentId ) throws Exception
{
ID tempId = oldParentId;
List<ID> parentIds = new ArrayList<ID>();
// find all the parents of the oldParentId
while ( ( tempId != null ) && !tempId.equals( getRootId() ) && !tempId.equals( getSuffixId() ) )
{
parentIds.add( tempId );
tempId = getParentId( tempId );
}
// find all the children of the childId
Cursor<IndexEntry<ID, E, ID>> cursor = subLevelIdx.forwardCursor( entryId );
List<ID> childIds = new ArrayList<ID>();
childIds.add( entryId );
while ( cursor.next() )
{
childIds.add( cursor.get().getId() );
}
// detach the childId and all its children from oldParentId and all it parents excluding the root
for ( ID pid : parentIds )
{
for ( ID cid : childIds )
{
subLevelIdx.drop( pid, cid );
}
}
parentIds.clear();
tempId = newParentId;
// find all the parents of the newParentId
while ( ( tempId != null) && !tempId.equals( getRootId() ) && !tempId.equals( getSuffixId() ) )
{
parentIds.add( tempId );
tempId = getParentId( tempId );
}
// attach the childId and all its children to newParentId and all it parents excluding the root
for ( ID id : parentIds )
{
for ( ID cid : childIds )
{
subLevelIdx.add( id, cid );
}
}
}
/**
* updates the CSN index
*
* @param entry the entry having entryCSN attribute
* @param id ID of the entry
* @throws Exception
*/
private void updateCsnIndex( Entry entry, ID id ) throws Exception
{
entryCsnIdx.drop( id );
entryCsnIdx.add( entry.get( SchemaConstants.ENTRY_CSN_AT ).getString(), id );
}
/**
* @return true if the hasEntry check is performed before adding a entry, false otherwise
*/
public boolean isCheckHasEntryDuringAdd()
{
return checkHasEntryDuringAdd;
}
/**
* set the flag to nable/disable checking of entry existence before actually adding it
* @param checkHasEntryDuringAdd
*/
public void setCheckHasEntryDuringAdd( boolean checkHasEntryDuringAdd )
{
this.checkHasEntryDuringAdd = checkHasEntryDuringAdd;
}
}