| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.server.core.partition.impl.btree; |
| |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.net.URI; |
| import java.util.Arrays; |
| 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.Semaphore; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.locks.ReadWriteLock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| import org.ehcache.Cache; |
| import org.ehcache.config.CacheConfiguration; |
| |
| import org.apache.directory.api.ldap.model.constants.SchemaConstants; |
| import org.apache.directory.api.ldap.model.cursor.Cursor; |
| import org.apache.directory.api.ldap.model.cursor.CursorException; |
| import org.apache.directory.api.ldap.model.entry.Attribute; |
| import org.apache.directory.api.ldap.model.entry.Entry; |
| import org.apache.directory.api.ldap.model.entry.Modification; |
| import org.apache.directory.api.ldap.model.entry.Value; |
| import org.apache.directory.api.ldap.model.exception.LdapAliasDereferencingException; |
| import org.apache.directory.api.ldap.model.exception.LdapAliasException; |
| import org.apache.directory.api.ldap.model.exception.LdapContextNotEmptyException; |
| import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException; |
| import org.apache.directory.api.ldap.model.exception.LdapException; |
| import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; |
| import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException; |
| import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException; |
| import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException; |
| import org.apache.directory.api.ldap.model.exception.LdapOtherException; |
| import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException; |
| import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException; |
| import org.apache.directory.api.ldap.model.message.ResultCodeEnum; |
| import org.apache.directory.api.ldap.model.name.Ava; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.api.ldap.model.name.Rdn; |
| import org.apache.directory.api.ldap.model.schema.AttributeType; |
| import org.apache.directory.api.ldap.model.schema.MatchingRule; |
| import org.apache.directory.api.ldap.model.schema.Normalizer; |
| import org.apache.directory.api.ldap.model.schema.SchemaManager; |
| import org.apache.directory.api.util.Strings; |
| import org.apache.directory.api.util.exception.MultiException; |
| import org.apache.directory.server.constants.ApacheSchemaConstants; |
| import org.apache.directory.server.core.api.DnFactory; |
| import org.apache.directory.server.core.api.entry.ClonedServerEntry; |
| import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; |
| import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl; |
| import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.ModDnAva; |
| import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.OperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.UnbindOperationContext; |
| import org.apache.directory.server.core.api.partition.AbstractPartition; |
| import org.apache.directory.server.core.api.partition.Partition; |
| import org.apache.directory.server.core.api.partition.PartitionTxn; |
| import org.apache.directory.server.core.api.partition.PartitionWriteTxn; |
| import org.apache.directory.server.core.api.partition.Subordinates; |
| import org.apache.directory.server.i18n.I18n; |
| import org.apache.directory.server.xdbm.Index; |
| import org.apache.directory.server.xdbm.IndexEntry; |
| import org.apache.directory.server.xdbm.IndexNotFoundException; |
| import org.apache.directory.server.xdbm.MasterTable; |
| import org.apache.directory.server.xdbm.ParentIdAndRdn; |
| import org.apache.directory.server.xdbm.Store; |
| import org.apache.directory.server.xdbm.search.Optimizer; |
| import org.apache.directory.server.xdbm.search.PartitionSearchResult; |
| import org.apache.directory.server.xdbm.search.SearchEngine; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * An abstract {@link Partition} that uses general BTree operations. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public abstract class AbstractBTreePartition extends AbstractPartition implements Store |
| { |
| /** static logger */ |
| private static final Logger LOG = LoggerFactory.getLogger( AbstractBTreePartition.class ); |
| |
| /** the search engine used to search the database */ |
| private SearchEngine searchEngine; |
| |
| /** The optimizer to use during search operation */ |
| private Optimizer optimizer; |
| |
| /** Tells if the Optimizer is enabled */ |
| protected boolean optimizerEnabled = true; |
| |
| /** The default cache size is set to 10 000 objects */ |
| public static final int DEFAULT_CACHE_SIZE = 10000; |
| |
| /** The Entry cache size for this partition */ |
| protected int cacheSize = DEFAULT_CACHE_SIZE; |
| |
| /** The alias cache */ |
| protected Cache< String, Dn > aliasCache; |
| |
| /** The ParentIdAndRdn cache */ |
| protected Cache< String, ParentIdAndRdn > piarCache; |
| |
| /** true if we sync disks on every write operation */ |
| protected AtomicBoolean isSyncOnWrite = new AtomicBoolean( true ); |
| |
| /** The suffix UUID */ |
| private volatile String suffixId; |
| |
| /** The path in which this Partition stores files */ |
| protected URI partitionPath; |
| |
| /** The set of indexed attributes */ |
| private Set<Index<?, String>> indexedAttributes; |
| |
| /** the master table storing entries by primary key */ |
| protected MasterTable master; |
| |
| /** a map of attributeType numeric UUID to user userIndices */ |
| protected Map<String, Index<?, String>> userIndices = new HashMap<>(); |
| |
| /** a map of attributeType numeric UUID to system userIndices */ |
| protected Map<String, Index<?, String>> systemIndices = new HashMap<>(); |
| |
| /** the relative distinguished name index */ |
| protected Index<ParentIdAndRdn, String> rdnIdx; |
| |
| /** a system index on objectClass attribute*/ |
| protected Index<String, String> objectClassIdx; |
| |
| /** the attribute presence index */ |
| protected Index<String, String> presenceIdx; |
| |
| /** a system index on entryCSN attribute */ |
| protected Index<String, String> entryCsnIdx; |
| |
| /** a system index on aliasedObjectName attribute */ |
| protected Index<Dn, String> aliasIdx; |
| |
| /** the subtree scope alias index */ |
| protected Index<String, String> subAliasIdx; |
| |
| /** the one level scope alias index */ |
| protected Index<String, String> oneAliasIdx; |
| |
| /** a system index on administrativeRole attribute */ |
| protected Index<String, String> adminRoleIdx; |
| |
| /** Cached attributes types to avoid lookup all over the code */ |
| protected AttributeType objectClassAT; |
| private Normalizer objectClassNormalizer; |
| protected AttributeType presenceAT; |
| private Normalizer presenceNormalizer; |
| protected AttributeType entryCsnAT; |
| protected AttributeType entryDnAT; |
| protected AttributeType entryUuidAT; |
| protected AttributeType aliasedObjectNameAT; |
| protected AttributeType administrativeRoleAT; |
| protected AttributeType contextCsnAT; |
| |
| /** Cached value for TOP */ |
| private Value topOCValue; |
| |
| private static final boolean NO_REVERSE = Boolean.FALSE; |
| private static final boolean WITH_REVERSE = Boolean.TRUE; |
| |
| protected static final boolean ADD_CHILD = true; |
| protected static final boolean REMOVE_CHILD = false; |
| |
| /** A lock to protect the backend from concurrent reads/writes */ |
| private ReadWriteLock rwLock; |
| |
| /** a cache to hold <entryUUID, Dn> pairs, this is used for speeding up the buildEntryDn() method */ |
| private Cache<String, Dn> entryDnCache; |
| |
| /** a semaphore to serialize the writes on context entry while updating contextCSN attribute */ |
| private Semaphore ctxCsnSemaphore = new Semaphore( 1 ); |
| |
| // ------------------------------------------------------------------------ |
| // C O N S T R U C T O R S |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Creates a B-tree based context partition. |
| * |
| * @param schemaManager the schema manager |
| */ |
| protected AbstractBTreePartition( SchemaManager schemaManager ) |
| { |
| this.schemaManager = schemaManager; |
| |
| initInstance(); |
| } |
| |
| |
| /** |
| * Creates a B-tree based context partition. |
| * |
| * @param schemaManager the schema manager |
| * @param dnFactory the DN factory |
| */ |
| protected AbstractBTreePartition( SchemaManager schemaManager, DnFactory dnFactory ) |
| { |
| this.schemaManager = schemaManager; |
| this.dnFactory = dnFactory; |
| |
| initInstance(); |
| } |
| |
| |
| /** |
| * Intializes the instance. |
| */ |
| private void initInstance() |
| { |
| indexedAttributes = new HashSet<>(); |
| |
| // Initialize Attribute types used all over this method |
| objectClassAT = schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT ); |
| objectClassNormalizer = objectClassAT.getEquality().getNormalizer(); |
| presenceAT = schemaManager.getAttributeType( ApacheSchemaConstants.APACHE_PRESENCE_AT ); |
| presenceNormalizer = presenceAT.getEquality().getNormalizer(); |
| aliasedObjectNameAT = schemaManager.getAttributeType( SchemaConstants.ALIASED_OBJECT_NAME_AT ); |
| entryCsnAT = schemaManager.getAttributeType( SchemaConstants.ENTRY_CSN_AT ); |
| entryDnAT = schemaManager.getAttributeType( SchemaConstants.ENTRY_DN_AT ); |
| entryUuidAT = schemaManager.getAttributeType( SchemaConstants.ENTRY_UUID_AT ); |
| administrativeRoleAT = schemaManager.getAttributeType( SchemaConstants.ADMINISTRATIVE_ROLE_AT ); |
| contextCsnAT = schemaManager.getAttributeType( SchemaConstants.CONTEXT_CSN_AT ); |
| |
| // Initialize a Value for TOP_OC |
| try |
| { |
| topOCValue = new Value( schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT_OID ), SchemaConstants.TOP_OC_OID ); |
| } |
| catch ( LdapInvalidAttributeValueException e ) |
| { |
| // There is nothing we can do... |
| } |
| |
| // Relax the entryDnAT so that we don't check the EntryDN twice |
| entryDnAT.setRelaxed( true ); |
| } |
| |
| |
| // ------------------------------------------------------------------------ |
| // C O N F I G U R A T I O N M E T H O D S |
| // ------------------------------------------------------------------------ |
| /** |
| * Gets the entry cache size for this BTreePartition. |
| * |
| * @return the maximum size of the cache as the number of entries maximum before paging out |
| */ |
| @Override |
| public int getCacheSize() |
| { |
| return cacheSize; |
| } |
| |
| |
| /** |
| * Used to specify the entry cache size for a Partition. Various Partition |
| * implementations may interpret this value in different ways: i.e. total cache |
| * size limit verses the number of entries to cache. |
| * |
| * @param cacheSize the maximum size of the cache in the number of entries |
| */ |
| @Override |
| public void setCacheSize( int cacheSize ) |
| { |
| this.cacheSize = cacheSize; |
| } |
| |
| |
| /** |
| * Tells if the Optimizer is enabled or not |
| * @return true if the optimizer is enabled |
| */ |
| public boolean isOptimizerEnabled() |
| { |
| return optimizerEnabled; |
| } |
| |
| |
| /** |
| * Set the optimizer flag |
| * @param optimizerEnabled The flag |
| */ |
| public void setOptimizerEnabled( boolean optimizerEnabled ) |
| { |
| this.optimizerEnabled = optimizerEnabled; |
| } |
| |
| |
| /** |
| * Sets the path in which this Partition stores data. This may be an URL to |
| * a file or directory, or an JDBC URL. |
| * |
| * @param partitionPath the path in which this Partition stores data. |
| */ |
| @Override |
| public void setPartitionPath( URI partitionPath ) |
| { |
| checkInitialized( "partitionPath" ); |
| this.partitionPath = partitionPath; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isSyncOnWrite() |
| { |
| return isSyncOnWrite.get(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setSyncOnWrite( boolean isSyncOnWrite ) |
| { |
| checkInitialized( "syncOnWrite" ); |
| this.isSyncOnWrite.set( isSyncOnWrite ); |
| } |
| |
| |
| /** |
| * Sets up the system indices. |
| * |
| * @throws LdapException If the setup failed |
| */ |
| @SuppressWarnings("unchecked") |
| protected void setupSystemIndices() throws LdapException |
| { |
| // add missing system indices |
| if ( getPresenceIndex() == null ) |
| { |
| Index<String, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_PRESENCE_AT_OID, |
| partitionPath, NO_REVERSE ); |
| addIndex( index ); |
| } |
| |
| if ( getRdnIndex() == null ) |
| { |
| Index<ParentIdAndRdn, String> index = createSystemIndex( |
| ApacheSchemaConstants.APACHE_RDN_AT_OID, |
| partitionPath, WITH_REVERSE ); |
| addIndex( index ); |
| } |
| |
| if ( getAliasIndex() == null ) |
| { |
| Index<Dn, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_ALIAS_AT_OID, |
| partitionPath, WITH_REVERSE ); |
| addIndex( index ); |
| } |
| |
| if ( getOneAliasIndex() == null ) |
| { |
| Index<String, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID, |
| partitionPath, NO_REVERSE ); |
| addIndex( index ); |
| } |
| |
| if ( getSubAliasIndex() == null ) |
| { |
| Index<String, String> index = createSystemIndex( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID, |
| partitionPath, NO_REVERSE ); |
| addIndex( index ); |
| } |
| |
| if ( getObjectClassIndex() == null ) |
| { |
| Index<String, String> index = createSystemIndex( SchemaConstants.OBJECT_CLASS_AT_OID, partitionPath, |
| NO_REVERSE ); |
| addIndex( index ); |
| } |
| |
| if ( getEntryCsnIndex() == null ) |
| { |
| Index<String, String> index = createSystemIndex( SchemaConstants.ENTRY_CSN_AT_OID, partitionPath, |
| NO_REVERSE ); |
| addIndex( index ); |
| } |
| |
| if ( getAdministrativeRoleIndex() == null ) |
| { |
| Index<String, String> index = createSystemIndex( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID, |
| partitionPath, |
| NO_REVERSE ); |
| addIndex( index ); |
| } |
| |
| // convert and initialize system indices |
| for ( Map.Entry<String, Index<?, String>> elem : systemIndices.entrySet() ) |
| { |
| Index<?, String> index = elem.getValue(); |
| index = convertAndInit( index ); |
| systemIndices.put( elem.getKey(), index ); |
| } |
| |
| // set index shortcuts |
| rdnIdx = ( Index<ParentIdAndRdn, String> ) systemIndices |
| .get( ApacheSchemaConstants.APACHE_RDN_AT_OID ); |
| presenceIdx = ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_PRESENCE_AT_OID ); |
| aliasIdx = ( Index<Dn, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ); |
| oneAliasIdx = ( Index<String, String> ) systemIndices |
| .get( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID ); |
| subAliasIdx = ( Index<String, String> ) systemIndices |
| .get( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID ); |
| objectClassIdx = ( Index<String, String> ) systemIndices.get( SchemaConstants.OBJECT_CLASS_AT_OID ); |
| entryCsnIdx = ( Index<String, String> ) systemIndices.get( SchemaConstants.ENTRY_CSN_AT_OID ); |
| adminRoleIdx = ( Index<String, String> ) systemIndices.get( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ); |
| } |
| |
| |
| /** |
| * Sets up the user indices. |
| * |
| * @throws LdapException If the setup failed |
| */ |
| protected void setupUserIndices() throws LdapException |
| { |
| // convert and initialize system indices |
| Map<String, Index<?, String>> tmp = new HashMap<>(); |
| |
| for ( Map.Entry<String, Index<?, String>> elem : userIndices.entrySet() ) |
| { |
| String oid = elem.getKey(); |
| |
| // check that the attributeType has an EQUALITY matchingRule |
| AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid ); |
| MatchingRule mr = attributeType.getEquality(); |
| |
| if ( mr != null ) |
| { |
| Index<?, String> index = elem.getValue(); |
| index = convertAndInit( index ); |
| tmp.put( oid, index ); |
| } |
| else |
| { |
| LOG.error( I18n.err( I18n.ERR_4, attributeType.getName() ) ); |
| } |
| } |
| |
| userIndices = tmp; |
| } |
| |
| |
| /** |
| * Gets the DefaultSearchEngine used by this ContextPartition to search the |
| * Database. |
| * |
| * @return the search engine |
| */ |
| public SearchEngine getSearchEngine() |
| { |
| return searchEngine; |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // Miscellaneous abstract methods |
| // ----------------------------------------------------------------------- |
| /** |
| * Convert and initialize an index for a specific store implementation. |
| * |
| * @param index the index |
| * @return the converted and initialized index |
| * @throws LdapException If teh conversion failed |
| */ |
| protected abstract Index<?, String> convertAndInit( Index<?, String> index ) throws LdapException; |
| |
| |
| /** |
| * Gets the path in which this Partition stores data. |
| * |
| * @return the path in which this Partition stores data. |
| */ |
| @Override |
| public URI getPartitionPath() |
| { |
| return partitionPath; |
| } |
| |
| |
| // ------------------------------------------------------------------------ |
| // Partition Interface Method Implementations |
| // ------------------------------------------------------------------------ |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| protected void doDestroy( PartitionTxn partitionTxn ) throws LdapException |
| { |
| LOG.debug( "destroy() called on store for {}", this.suffixDn ); |
| |
| if ( !initialized ) |
| { |
| return; |
| } |
| |
| // don't reset initialized flag |
| initialized = false; |
| |
| entryDnCache.clear(); |
| |
| MultiException errors = new MultiException( I18n.err( I18n.ERR_577 ) ); |
| |
| for ( Index<?, String> index : userIndices.values() ) |
| { |
| try |
| { |
| index.close( partitionTxn ); |
| LOG.debug( "Closed {} user index for {} partition.", index.getAttributeId(), suffixDn ); |
| } |
| catch ( Throwable t ) |
| { |
| LOG.error( I18n.err( I18n.ERR_124 ), t ); |
| errors.addThrowable( t ); |
| } |
| } |
| |
| for ( Index<?, String> index : systemIndices.values() ) |
| { |
| try |
| { |
| index.close( partitionTxn ); |
| LOG.debug( "Closed {} system index for {} partition.", index.getAttributeId(), suffixDn ); |
| } |
| catch ( Throwable t ) |
| { |
| LOG.error( I18n.err( I18n.ERR_124 ), t ); |
| errors.addThrowable( t ); |
| } |
| } |
| |
| try |
| { |
| master.close( partitionTxn ); |
| |
| if ( LOG.isDebugEnabled() ) |
| { |
| LOG.debug( I18n.err( I18n.ERR_125, suffixDn ) ); |
| } |
| } |
| catch ( Throwable t ) |
| { |
| LOG.error( I18n.err( I18n.ERR_126 ), t ); |
| errors.addThrowable( t ); |
| } |
| |
| if ( errors.size() > 0 ) |
| { |
| throw new LdapOtherException( errors.getMessage(), errors ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void repair() throws LdapException |
| { |
| // Do nothing by default |
| doRepair(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| protected void doInit() throws LdapException |
| { |
| // First, inject the indexed attributes if any |
| if ( ( indexedAttributes != null ) && ( !indexedAttributes.isEmpty() ) ) |
| { |
| for ( Index index : indexedAttributes ) |
| { |
| addIndex( index ); |
| } |
| } |
| |
| // Now, initialize the configured index |
| setupSystemIndices(); |
| setupUserIndices(); |
| |
| if ( cacheService != null ) |
| { |
| aliasCache = cacheService.getCache( "alias", String.class, Dn.class ); |
| |
| CacheConfiguration< String, Dn > aliasCacheConfig = aliasCache.getRuntimeConfiguration(); |
| |
| piarCache = cacheService.getCache( "piar", String.class, ParentIdAndRdn.class ); |
| |
| entryDnCache = cacheService.getCache( "entryDn", String.class, Dn.class ); |
| } |
| } |
| |
| |
| private void dumpAllRdnIdx( PartitionTxn partitionTxn ) throws LdapException, CursorException, IOException |
| { |
| if ( LOG.isDebugEnabled() ) |
| { |
| dumpRdnIdx( partitionTxn, Partition.ROOT_ID, "" ); |
| System.out.println( "-----------------------------" ); |
| } |
| } |
| |
| |
| private void dumpRdnIdx( PartitionTxn partitionTxn ) throws LdapException, CursorException, IOException |
| { |
| if ( LOG.isDebugEnabled() ) |
| { |
| dumpRdnIdx( partitionTxn, Partition.ROOT_ID, 1, "" ); |
| System.out.println( "-----------------------------" ); |
| } |
| } |
| |
| |
| /** |
| * Dump the RDN index content |
| * |
| * @param partitionTxn The transaction to use |
| * @param id The root ID |
| * @param tabs The space prefix |
| * @throws LdapException If we had an issue while dumping the Rdn index |
| * @throws CursorException If the cursor failed to browse the Rdn Index |
| * @throws IOException If we weren't able to read teh Rdn Index file |
| */ |
| public void dumpRdnIdx( PartitionTxn partitionTxn, String id, String tabs ) throws LdapException, CursorException, IOException |
| { |
| // Start with the root |
| Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = rdnIdx.forwardCursor( partitionTxn ); |
| |
| IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>(); |
| startingPos.setKey( new ParentIdAndRdn( id, ( Rdn[] ) null ) ); |
| cursor.before( startingPos ); |
| |
| while ( cursor.next() ) |
| { |
| IndexEntry<ParentIdAndRdn, String> entry = cursor.get(); |
| System.out.println( tabs + entry ); |
| } |
| |
| cursor.close(); |
| } |
| |
| |
| private void dumpRdnIdx( PartitionTxn partitionTxn, String id, int nbSibbling, String tabs ) |
| throws LdapException, CursorException, IOException |
| { |
| // Start with the root |
| Cursor<IndexEntry<ParentIdAndRdn, String>> cursor = rdnIdx.forwardCursor( partitionTxn ); |
| |
| IndexEntry<ParentIdAndRdn, String> startingPos = new IndexEntry<>(); |
| startingPos.setKey( new ParentIdAndRdn( id, ( Rdn[] ) null ) ); |
| cursor.before( startingPos ); |
| int countChildren = 0; |
| |
| while ( cursor.next() && ( countChildren < nbSibbling ) ) |
| { |
| IndexEntry<ParentIdAndRdn, String> entry = cursor.get(); |
| System.out.println( tabs + entry ); |
| countChildren++; |
| |
| // And now, the children |
| int nbChildren = entry.getKey().getNbChildren(); |
| |
| if ( nbChildren > 0 ) |
| { |
| dumpRdnIdx( partitionTxn, entry.getId(), nbChildren, tabs + " " ); |
| } |
| } |
| |
| cursor.close(); |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // The Add operation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void add( AddOperationContext addContext ) throws LdapException |
| { |
| PartitionTxn partitionTxn = addContext.getTransaction(); |
| |
| assert ( partitionTxn != null ); |
| assert ( partitionTxn instanceof PartitionWriteTxn ); |
| |
| try |
| { |
| setRWLock( addContext ); |
| Entry entry = ( ( ClonedServerEntry ) addContext.getEntry() ).getClonedEntry(); |
| |
| Dn entryDn = entry.getDn(); |
| |
| // check if the entry already exists |
| lockRead(); |
| |
| try |
| { |
| if ( getEntryId( partitionTxn, entryDn ) != null ) |
| { |
| throw new LdapEntryAlreadyExistsException( |
| I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, entryDn.getName() ) ); |
| } |
| } |
| finally |
| { |
| unlockRead(); |
| } |
| |
| String parentId = null; |
| |
| // |
| // 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 key; |
| |
| if ( entryDn.getNormName().equals( suffixDn.getNormName() ) ) |
| { |
| parentId = Partition.ROOT_ID; |
| key = new ParentIdAndRdn( parentId, suffixDn.getRdns() ); |
| } |
| else |
| { |
| parentDn = entryDn.getParent(); |
| |
| lockRead(); |
| |
| try |
| { |
| parentId = getEntryId( partitionTxn, parentDn ); |
| } |
| finally |
| { |
| unlockRead(); |
| } |
| |
| key = new ParentIdAndRdn( 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_ID_FOR_PARENT_NOT_FOUND, parentDn ) ); |
| } |
| |
| // Get a new UUID for the added entry if it does not have any already |
| Attribute entryUUID = entry.get( entryUuidAT ); |
| |
| String id; |
| |
| if ( entryUUID == null ) |
| { |
| id = master.getNextId( entry ); |
| } |
| else |
| { |
| id = entryUUID.getString(); |
| } |
| |
| if ( entryDn.getNormName().equals( suffixDn.getNormName() ) ) |
| { |
| suffixId = id; |
| } |
| |
| // Update the ObjectClass index |
| Attribute objectClass = entry.get( objectClassAT ); |
| |
| if ( objectClass == null ) |
| { |
| String msg = I18n.err( I18n.ERR_217, entryDn.getName(), entry ); |
| ResultCodeEnum rc = ResultCodeEnum.OBJECT_CLASS_VIOLATION; |
| |
| throw new LdapSchemaViolationException( rc, msg ); |
| } |
| |
| for ( Value value : objectClass ) |
| { |
| if ( value.equals( topOCValue ) ) |
| { |
| continue; |
| } |
| |
| String normalizedOc = objectClassNormalizer.normalize( value.getString() ); |
| |
| objectClassIdx.add( partitionTxn, normalizedOc, id ); |
| } |
| |
| if ( objectClass.contains( SchemaConstants.ALIAS_OC ) ) |
| { |
| Attribute aliasAttr = entry.get( aliasedObjectNameAT ); |
| |
| addAliasIndices( partitionTxn, id, entryDn, new Dn( schemaManager, aliasAttr.getString() ) ); |
| } |
| |
| // Update the EntryCsn index |
| Attribute entryCsn = entry.get( entryCsnAT ); |
| |
| if ( entryCsn == null ) |
| { |
| String msg = I18n.err( I18n.ERR_219, entryDn.getName(), entry ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, msg ); |
| } |
| |
| entryCsnIdx.add( partitionTxn, entryCsn.getString(), id ); |
| |
| // Update the AdministrativeRole index, if needed |
| if ( entry.containsAttribute( administrativeRoleAT ) ) |
| { |
| // We may have more than one role |
| Attribute adminRoles = entry.get( administrativeRoleAT ); |
| |
| for ( Value value : adminRoles ) |
| { |
| adminRoleIdx.add( partitionTxn, value.getString(), id ); |
| } |
| |
| // Adds only those attributes that are indexed |
| presenceIdx.add( partitionTxn, administrativeRoleAT.getOid(), id ); |
| } |
| |
| // Now work on the user defined userIndices |
| for ( Attribute attribute : entry ) |
| { |
| AttributeType attributeType = attribute.getAttributeType(); |
| String attributeOid = attributeType.getOid(); |
| |
| if ( hasUserIndexOn( attributeType ) ) |
| { |
| Index<Object, String> userIndex = ( Index<Object, String> ) getUserIndex( attributeType ); |
| |
| // here lookup by attributeId is OK since we got attributeId from |
| // the entry via the enumeration - it's in there as is for sure |
| |
| for ( Value value : attribute ) |
| { |
| String normalized = value.getNormalized(); |
| userIndex.add( partitionTxn, normalized, id ); |
| } |
| |
| // Adds only those attributes that are indexed |
| presenceIdx.add( partitionTxn, attributeOid, id ); |
| } |
| } |
| |
| // Add the parentId in the entry |
| entry.put( ApacheSchemaConstants.ENTRY_PARENT_ID_AT, parentId ); |
| |
| lockWrite(); |
| |
| try |
| { |
| // Update the RDN index |
| rdnIdx.add( partitionTxn, key, id ); |
| |
| // Update the parent's nbChildren and nbDescendants values |
| if ( parentId != Partition.ROOT_ID ) |
| { |
| updateRdnIdx( partitionTxn, parentId, ADD_CHILD, 0 ); |
| } |
| |
| // Remove the EntryDN attribute |
| entry.removeAttributes( entryDnAT ); |
| |
| Attribute at = entry.get( SchemaConstants.ENTRY_CSN_AT ); |
| setContextCsn( at.getString() ); |
| |
| // And finally add the entry into the master table |
| master.put( partitionTxn, id, entry ); |
| } |
| finally |
| { |
| unlockWrite(); |
| } |
| } |
| catch ( LdapException le ) |
| { |
| throw le; |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapException( e ); |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // The Delete operation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Entry delete( DeleteOperationContext deleteContext ) throws LdapException |
| { |
| PartitionTxn partitionTxn = deleteContext.getTransaction(); |
| |
| assert ( partitionTxn != null ); |
| assert ( partitionTxn instanceof PartitionWriteTxn ); |
| |
| setRWLock( deleteContext ); |
| Dn dn = deleteContext.getDn(); |
| String id = null; |
| |
| lockRead(); |
| |
| try |
| { |
| id = getEntryId( partitionTxn, dn ); |
| } |
| finally |
| { |
| unlockRead(); |
| } |
| |
| // don't continue if id is null |
| if ( id == null ) |
| { |
| throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_699, dn ) ); |
| } |
| |
| long childCount = getChildCount( partitionTxn, id ); |
| |
| if ( childCount > 0 ) |
| { |
| throw new LdapContextNotEmptyException( I18n.err( I18n.ERR_700, dn ) ); |
| } |
| |
| // We now defer the deletion to the implementing class |
| Entry deletedEntry = delete( partitionTxn, id ); |
| |
| updateCache( deleteContext ); |
| |
| return deletedEntry; |
| } |
| |
| |
| protected void updateRdnIdx( PartitionTxn partitionTxn, String parentId, boolean addRemove, int nbDescendant ) throws LdapException |
| { |
| boolean isFirst = true; |
| ////dumpRdnIdx(); |
| |
| if ( parentId.equals( Partition.ROOT_ID ) ) |
| { |
| return; |
| } |
| |
| ParentIdAndRdn parent = rdnIdx.reverseLookup( partitionTxn, parentId ); |
| |
| while ( parent != null ) |
| { |
| rdnIdx.drop( partitionTxn, parentId ); |
| ////dumpRdnIdx(); |
| |
| if ( isFirst ) |
| { |
| if ( addRemove == ADD_CHILD ) |
| { |
| parent.setNbChildren( parent.getNbChildren() + 1 ); |
| } |
| else |
| { |
| parent.setNbChildren( parent.getNbChildren() - 1 ); |
| } |
| |
| isFirst = false; |
| } |
| |
| if ( addRemove == ADD_CHILD ) |
| { |
| parent.setNbDescendants( parent.getNbDescendants() + ( nbDescendant + 1 ) ); |
| } |
| else |
| { |
| parent.setNbDescendants( parent.getNbDescendants() - ( nbDescendant + 1 ) ); |
| } |
| |
| // Inject the modified element into the index |
| rdnIdx.add( partitionTxn, parent, parentId ); |
| |
| ////dumpRdnIdx(); |
| |
| parentId = parent.getParentId(); |
| parent = rdnIdx.reverseLookup( partitionTxn, parentId ); |
| } |
| } |
| |
| |
| /** |
| * Delete the entry associated with a given Id |
| * |
| * @param partitionTxn The transaction to use |
| * @param id The id of the entry to delete |
| * @return the deleted entry if found |
| * @throws LdapException If the deletion failed |
| */ |
| @Override |
| public Entry delete( PartitionTxn partitionTxn, String id ) throws LdapException |
| { |
| try |
| { |
| // First get the entry |
| Entry entry = null; |
| |
| lockRead(); |
| |
| try |
| { |
| entry = master.get( partitionTxn, id ); |
| } |
| finally |
| { |
| unlockRead(); |
| } |
| |
| if ( entry == null ) |
| { |
| // Not allowed |
| throw new LdapNoSuchObjectException( "Cannot find an entry for UUID " + id ); |
| } |
| |
| Attribute objectClass = entry.get( objectClassAT ); |
| |
| if ( objectClass.contains( SchemaConstants.ALIAS_OC ) ) |
| { |
| dropAliasIndices( partitionTxn, id ); |
| } |
| |
| // Update the ObjectClass index |
| for ( Value value : objectClass ) |
| { |
| if ( value.equals( topOCValue ) ) |
| { |
| continue; |
| } |
| |
| String normalizedOc = objectClassNormalizer.normalize( value.getString() ); |
| |
| objectClassIdx.drop( partitionTxn, normalizedOc, id ); |
| } |
| |
| // Update the parent's nbChildren and nbDescendants values |
| ParentIdAndRdn parent = rdnIdx.reverseLookup( partitionTxn, id ); |
| updateRdnIdx( partitionTxn, parent.getParentId(), REMOVE_CHILD, 0 ); |
| |
| // Update the rdn, oneLevel, subLevel, and entryCsn indexes |
| entryCsnIdx.drop( partitionTxn, entry.get( entryCsnAT ).getString(), id ); |
| |
| // Update the AdministrativeRole index, if needed |
| if ( entry.containsAttribute( administrativeRoleAT ) ) |
| { |
| // We may have more than one role |
| Attribute adminRoles = entry.get( administrativeRoleAT ); |
| |
| for ( Value value : adminRoles ) |
| { |
| adminRoleIdx.drop( partitionTxn, value.getString(), id ); |
| } |
| |
| // Deletes only those attributes that are indexed |
| presenceIdx.drop( partitionTxn, administrativeRoleAT.getOid(), id ); |
| } |
| |
| // Update the user indexes |
| for ( Attribute attribute : entry ) |
| { |
| AttributeType attributeType = attribute.getAttributeType(); |
| String attributeOid = attributeType.getOid(); |
| |
| if ( hasUserIndexOn( attributeType ) ) |
| { |
| Index<?, String> userIndex = getUserIndex( attributeType ); |
| |
| // here lookup by attributeId is ok since we got attributeId from |
| // the entry via the enumeration - it's in there as is for sure |
| for ( Value value : attribute ) |
| { |
| String normalized = value.getNormalized(); |
| ( ( Index ) userIndex ).drop( partitionTxn, normalized, id ); |
| } |
| |
| presenceIdx.drop( partitionTxn, attributeOid, id ); |
| } |
| } |
| |
| lockWrite(); |
| |
| try |
| { |
| rdnIdx.drop( partitionTxn, id ); |
| |
| ////dumpRdnIdx(); |
| |
| entryDnCache.remove( id ); |
| |
| Attribute csn = entry.get( entryCsnAT ); |
| // can be null while doing subentry deletion |
| if ( csn != null ) |
| { |
| setContextCsn( csn.getString() ); |
| } |
| |
| master.remove( partitionTxn, id ); |
| } |
| finally |
| { |
| unlockWrite(); |
| } |
| |
| if ( isSyncOnWrite.get() ) |
| { |
| sync(); |
| } |
| |
| return entry; |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // The Search operation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException |
| { |
| PartitionTxn partitionTxn = searchContext.getTransaction(); |
| |
| assert ( partitionTxn != null ); |
| |
| try |
| { |
| setRWLock( searchContext ); |
| |
| if ( ctxCsnChanged && getSuffixDn().equals( searchContext.getDn() ) ) |
| { |
| try |
| { |
| ctxCsnSemaphore.acquire(); |
| saveContextCsn( partitionTxn ); |
| ctxCsnChanged = false; |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| finally |
| { |
| ctxCsnSemaphore.release(); |
| } |
| } |
| |
| PartitionSearchResult searchResult = searchEngine.computeResult( partitionTxn, schemaManager, searchContext ); |
| |
| Cursor<Entry> result = new EntryCursorAdaptor( partitionTxn, this, searchResult ); |
| |
| return new EntryFilteringCursorImpl( result, searchContext, schemaManager ); |
| } |
| catch ( LdapException le ) |
| { |
| // TODO: SearchEngine.cursor() should only throw LdapException, then the exception handling here can be removed |
| throw le; |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // The Lookup operation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Entry lookup( LookupOperationContext lookupContext ) throws LdapException |
| { |
| PartitionTxn partitionTxn = lookupContext.getTransaction(); |
| |
| assert ( partitionTxn != null ); |
| |
| try |
| { |
| setRWLock( lookupContext ); |
| String id = getEntryId( partitionTxn, lookupContext.getDn() ); |
| |
| if ( id == null ) |
| { |
| return null; |
| } |
| |
| if ( ctxCsnChanged && getSuffixDn().getNormName().equals( lookupContext.getDn().getNormName() ) ) |
| { |
| try |
| { |
| ctxCsnSemaphore.acquire(); |
| saveContextCsn( partitionTxn ); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| finally |
| { |
| ctxCsnSemaphore.release(); |
| } |
| } |
| |
| return fetch( partitionTxn, id, lookupContext.getDn() ); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage() ); |
| } |
| } |
| |
| |
| /** |
| * Get back an entry knowing its UUID |
| * |
| * @param partitionTxn The transaction to use |
| * @param id The Entry UUID we want to get back |
| * @return The found Entry, or null if not found |
| * @throws LdapException If the lookup failed for any reason (except a not found entry) |
| */ |
| @Override |
| public Entry fetch( PartitionTxn partitionTxn, String id ) throws LdapException |
| { |
| try |
| { |
| rwLock.readLock().lock(); |
| |
| if ( id == null ) |
| { |
| id = ""; |
| } |
| |
| Dn dn = buildEntryDn( partitionTxn, id ); |
| |
| return fetch( partitionTxn, id, dn ); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| finally |
| { |
| rwLock.readLock().unlock(); |
| } |
| } |
| |
| |
| /** |
| * Get back an entry knowing its UUID |
| * |
| * @param partitionTxn The transaction to use |
| * @param id The Entry UUID we want to get back |
| * @return The found Entry, or null if not found |
| * @throws LdapException If the lookup failed for any reason (except a not found entry) |
| */ |
| @Override |
| public Entry fetch( PartitionTxn partitionTxn, String id, Dn dn ) throws LdapException |
| { |
| try |
| { |
| Entry entry = lookupCache( id ); |
| |
| if ( entry != null ) |
| { |
| entry.setDn( dn ); |
| |
| entry = new ClonedServerEntry( entry ); |
| |
| // Replace the entry's DN with the provided one |
| Attribute entryDnAt = entry.get( entryDnAT ); |
| Value dnValue = new Value( entryDnAT, dn.getName(), dn.getNormName() ); |
| |
| if ( entryDnAt == null ) |
| { |
| entry.add( entryDnAT, dnValue ); |
| } |
| else |
| { |
| entryDnAt.clear(); |
| entryDnAt.add( dnValue ); |
| } |
| |
| return entry; |
| } |
| |
| try |
| { |
| rwLock.readLock().lock(); |
| entry = master.get( partitionTxn, id ); |
| } |
| finally |
| { |
| rwLock.readLock().unlock(); |
| } |
| |
| if ( entry != null ) |
| { |
| // We have to store the DN in this entry |
| entry.setDn( dn ); |
| |
| // always store original entry in the cache |
| addToCache( id, entry ); |
| |
| entry = new ClonedServerEntry( entry ); |
| |
| if ( !entry.containsAttribute( entryDnAT ) ) |
| { |
| entry.add( entryDnAT, dn.getName() ); |
| } |
| |
| return entry; |
| } |
| |
| return null; |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // The Modify operation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void modify( ModifyOperationContext modifyContext ) throws LdapException |
| { |
| PartitionTxn partitionTxn = modifyContext.getTransaction(); |
| |
| assert ( partitionTxn != null ); |
| assert ( partitionTxn instanceof PartitionWriteTxn ); |
| |
| try |
| { |
| setRWLock( modifyContext ); |
| |
| Entry modifiedEntry = modify( partitionTxn, modifyContext.getDn(), |
| modifyContext.getModItems().toArray( new Modification[] |
| {} ) ); |
| |
| modifyContext.setAlteredEntry( modifiedEntry ); |
| |
| updateCache( modifyContext ); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final synchronized Entry modify( PartitionTxn partitionTxn, Dn dn, Modification... mods ) throws LdapException |
| { |
| String id = getEntryId( partitionTxn, dn ); |
| Entry entry = master.get( partitionTxn, id ); |
| |
| for ( Modification mod : mods ) |
| { |
| Attribute attrMods = mod.getAttribute(); |
| |
| try |
| { |
| switch ( mod.getOperation() ) |
| { |
| case ADD_ATTRIBUTE: |
| modifyAdd( partitionTxn, id, entry, attrMods ); |
| break; |
| |
| case REMOVE_ATTRIBUTE: |
| modifyRemove( partitionTxn, id, entry, attrMods ); |
| break; |
| |
| case REPLACE_ATTRIBUTE: |
| modifyReplace( partitionTxn, id, entry, attrMods ); |
| break; |
| |
| default: |
| throw new LdapException( I18n.err( I18n.ERR_221 ) ); |
| } |
| } |
| catch ( IndexNotFoundException infe ) |
| { |
| throw new LdapOtherException( infe.getMessage(), infe ); |
| } |
| } |
| |
| updateCsnIndex( partitionTxn, entry, id ); |
| |
| // Remove the EntryDN |
| entry.removeAttributes( entryDnAT ); |
| |
| setContextCsn( entry.get( entryCsnAT ).getString() ); |
| |
| master.put( partitionTxn, id, entry ); |
| |
| return entry; |
| } |
| |
| |
| /** |
| * Adds a set of attribute values while affecting the appropriate userIndices. |
| * The entry is not persisted: it is only changed in anticipation for a put |
| * into the master table. |
| * |
| * @param partitionTxn The transaction to use |
| * @param id the primary key of the entry |
| * @param entry the entry to alter |
| * @param mods the attribute and values to add |
| * @throws Exception if index alteration or attribute addition fails |
| */ |
| @SuppressWarnings("unchecked") |
| private void modifyAdd( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) |
| throws LdapException, IndexNotFoundException |
| { |
| if ( entry instanceof ClonedServerEntry ) |
| { |
| throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) ); |
| } |
| |
| String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() ); |
| String normalizedModsOid = presenceNormalizer.normalize( modsOid ); |
| |
| AttributeType attributeType = mods.getAttributeType(); |
| |
| // Special case for the ObjectClass index |
| if ( modsOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID ) ) |
| { |
| for ( Value value : mods ) |
| { |
| if ( value.equals( topOCValue ) ) |
| { |
| continue; |
| } |
| |
| String normalizedOc = objectClassNormalizer.normalize( value.getString() ); |
| |
| objectClassIdx.add( partitionTxn, normalizedOc, id ); |
| } |
| } |
| else if ( hasUserIndexOn( attributeType ) ) |
| { |
| Index<?, String> userIndex = getUserIndex( attributeType ); |
| |
| if ( mods.size() > 0 ) |
| { |
| for ( Value value : mods ) |
| { |
| String normalized = value.getNormalized(); |
| ( ( Index ) userIndex ).add( partitionTxn, normalized, id ); |
| } |
| } |
| else |
| { |
| // Special case when we have null values |
| ( ( Index ) userIndex ).add( partitionTxn, null, id ); |
| } |
| |
| // If the attr didn't exist for this id add it to presence index |
| if ( !presenceIdx.forward( partitionTxn, normalizedModsOid, id ) ) |
| { |
| presenceIdx.add( partitionTxn, normalizedModsOid, id ); |
| } |
| } |
| // Special case for the AdministrativeRole index |
| else if ( modsOid.equals( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ) ) |
| { |
| // We may have more than one role |
| for ( Value value : mods ) |
| { |
| adminRoleIdx.add( partitionTxn, value.getString(), id ); |
| } |
| |
| // If the attr didn't exist for this id add it to presence index |
| if ( !presenceIdx.forward( partitionTxn, normalizedModsOid, id ) ) |
| { |
| presenceIdx.add( partitionTxn, normalizedModsOid, id ); |
| } |
| } |
| |
| // add all the values in mods to the same attribute in the entry |
| if ( mods.size() > 0 ) |
| { |
| for ( Value value : mods ) |
| { |
| entry.add( mods.getAttributeType(), value ); |
| } |
| } |
| else |
| { |
| // Special cases for null values |
| if ( mods.getAttributeType().getSyntax().isHumanReadable() ) |
| { |
| entry.add( mods.getAttributeType(), new Value( mods.getAttributeType(), ( String ) null ) ); |
| } |
| else |
| { |
| entry.add( mods.getAttributeType(), new Value( mods.getAttributeType(), ( byte[] ) null ) ); |
| } |
| } |
| |
| if ( modsOid.equals( SchemaConstants.ALIASED_OBJECT_NAME_AT_OID ) ) |
| { |
| Dn ndn = getEntryDn( partitionTxn, id ); |
| addAliasIndices( partitionTxn, id, ndn, new Dn( schemaManager, 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 partitionTxn The transaction to use |
| * @param id the primary key of the entry |
| * @param entry the entry to alter |
| * @param mods the replacement attribute and values |
| * @throws Exception if index alteration or attribute modification |
| * fails. |
| */ |
| @SuppressWarnings("unchecked") |
| private void modifyReplace( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) |
| throws LdapException, IndexNotFoundException |
| { |
| if ( entry instanceof ClonedServerEntry ) |
| { |
| throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) ); |
| } |
| |
| String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() ); |
| AttributeType attributeType = mods.getAttributeType(); |
| |
| // Special case for the ObjectClass index |
| if ( attributeType.equals( objectClassAT ) ) |
| { |
| // if the id exists in the index drop all existing attribute |
| // value index entries and add new ones |
| for ( Value value : entry.get( objectClassAT ) ) |
| { |
| if ( value.equals( topOCValue ) ) |
| { |
| continue; |
| } |
| |
| String normalizedOc = objectClassNormalizer.normalize( value.getString() ); |
| |
| objectClassIdx.drop( partitionTxn, normalizedOc, id ); |
| } |
| |
| for ( Value value : mods ) |
| { |
| if ( value.equals( topOCValue ) ) |
| { |
| continue; |
| } |
| |
| String normalizedOc = objectClassNormalizer.normalize( value.getString() ); |
| |
| objectClassIdx.add( partitionTxn, normalizedOc, id ); |
| } |
| } |
| else if ( hasUserIndexOn( attributeType ) ) |
| { |
| Index<?, String> userIndex = getUserIndex( attributeType ); |
| |
| // Drop all the previous values |
| Attribute oldAttribute = entry.get( mods.getAttributeType() ); |
| |
| if ( oldAttribute != null ) |
| { |
| for ( Value value : oldAttribute ) |
| { |
| String normalized = value.getNormalized(); |
| ( ( Index<Object, String> ) userIndex ).drop( partitionTxn, normalized, id ); |
| } |
| } |
| |
| // And add the new ones |
| for ( Value value : mods ) |
| { |
| String normalized = value.getNormalized(); |
| ( ( Index ) userIndex ).add( partitionTxn, normalized, id ); |
| } |
| |
| /* |
| * If we have no new value, we have to drop the AT fro the presence index |
| */ |
| if ( mods.size() == 0 ) |
| { |
| presenceIdx.drop( partitionTxn, modsOid, id ); |
| } |
| } |
| // Special case for the AdministrativeRole index |
| else if ( attributeType.equals( administrativeRoleAT ) ) |
| { |
| // Remove the previous values |
| for ( Value value : entry.get( administrativeRoleAT ) ) |
| { |
| if ( value.equals( topOCValue ) ) |
| { |
| continue; |
| } |
| |
| String normalizedOc = objectClassNormalizer.normalize( value.getString() ); |
| |
| objectClassIdx.drop( partitionTxn, normalizedOc, id ); |
| } |
| |
| // And add the new ones |
| for ( Value value : mods ) |
| { |
| String valueStr = value.getString(); |
| |
| if ( valueStr.equals( topOCValue ) ) |
| { |
| continue; |
| } |
| |
| adminRoleIdx.add( partitionTxn, valueStr, id ); |
| } |
| } |
| |
| String aliasAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName( |
| SchemaConstants.ALIASED_OBJECT_NAME_AT ); |
| |
| if ( mods.getAttributeType().equals( aliasedObjectNameAT ) ) |
| { |
| dropAliasIndices( partitionTxn, 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( partitionTxn, id ); |
| addAliasIndices( partitionTxn, id, entryDn, new Dn( schemaManager, 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 partitionTxn The transaction to use |
| * @param id the primary key of the entry |
| * @param entry the entry to alter |
| * @param mods the attribute and its values to delete |
| * @throws Exception if index alteration or attribute modification fails. |
| */ |
| @SuppressWarnings("unchecked") |
| private void modifyRemove( PartitionTxn partitionTxn, String id, Entry entry, Attribute mods ) |
| throws LdapException, IndexNotFoundException |
| { |
| if ( entry instanceof ClonedServerEntry ) |
| { |
| throw new LdapOtherException( I18n.err( I18n.ERR_215_CANNOT_STORE_CLONED_SERVER_ENTRY ) ); |
| } |
| |
| String modsOid = schemaManager.getAttributeTypeRegistry().getOidByName( mods.getId() ); |
| AttributeType attributeType = mods.getAttributeType(); |
| |
| // Special case for the ObjectClass index |
| if ( attributeType.equals( objectClassAT ) ) |
| { |
| /* |
| * 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 ) |
| { |
| for ( Value value : entry.get( objectClassAT ) ) |
| { |
| if ( value.equals( topOCValue ) ) |
| { |
| continue; |
| } |
| |
| String normalizedOc = objectClassNormalizer.normalize( value.getString() ); |
| |
| objectClassIdx.drop( partitionTxn, normalizedOc, id ); |
| } |
| } |
| else |
| { |
| for ( Value value : mods ) |
| { |
| if ( value.equals( topOCValue ) ) |
| { |
| continue; |
| } |
| |
| String normalizedOc = objectClassNormalizer.normalize( value.getString() ); |
| |
| objectClassIdx.drop( partitionTxn, normalizedOc, id ); |
| } |
| } |
| } |
| else if ( hasUserIndexOn( attributeType ) ) |
| { |
| Index<?, String> userIndex = getUserIndex( attributeType ); |
| |
| Attribute attribute = entry.get( attributeType ).clone(); |
| int nbValues = 0; |
| |
| if ( attribute != null ) |
| { |
| nbValues = attribute.size(); |
| } |
| |
| /* |
| * 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 ) userIndex ).drop( partitionTxn, id ); |
| nbValues = 0; |
| } |
| else if ( nbValues > 0 ) |
| { |
| for ( Value value : mods ) |
| { |
| if ( attribute.contains( value ) ) |
| { |
| nbValues--; |
| attribute.remove( value ); |
| } |
| |
| String normalized = value.getNormalized(); |
| ( ( Index ) userIndex ).drop( partitionTxn, normalized, id ); |
| } |
| } |
| |
| /* |
| * If no attribute values exist for this entryId in the index then |
| * we remove the presence index entry for the removed attribute. |
| */ |
| if ( nbValues == 0 ) |
| { |
| presenceIdx.drop( partitionTxn, modsOid, id ); |
| } |
| } |
| // Special case for the AdministrativeRole index |
| else if ( modsOid.equals( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ) ) |
| { |
| // We may have more than one role |
| for ( Value value : mods ) |
| { |
| adminRoleIdx.drop( partitionTxn, value.getString(), 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 == adminRoleIdx.reverseLookup( partitionTxn, id ) ) |
| { |
| presenceIdx.drop( partitionTxn, modsOid, id ); |
| } |
| } |
| |
| /* |
| * If there are no attribute values in the modifications then this |
| * implies the complete removal of the attribute from the entry. Else |
| * we remove individual attribute values from the entry in mods one |
| * at a time. |
| */ |
| if ( mods.size() == 0 ) |
| { |
| entry.removeAttributes( mods.getAttributeType() ); |
| } |
| else |
| { |
| Attribute entryAttr = entry.get( mods.getAttributeType() ); |
| |
| for ( Value value : mods ) |
| { |
| entryAttr.remove( value ); |
| } |
| |
| // if nothing is left just remove empty attribute |
| if ( entryAttr.size() == 0 ) |
| { |
| entry.removeAttributes( entryAttr.getId() ); |
| } |
| } |
| |
| // Aliases->single valued comp/partial attr removal is not relevant here |
| if ( mods.getAttributeType().equals( aliasedObjectNameAT ) ) |
| { |
| dropAliasIndices( partitionTxn, id ); |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // The Move operation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void move( MoveOperationContext moveContext ) throws LdapException |
| { |
| if ( moveContext.getNewSuperior().isDescendantOf( moveContext.getDn() ) ) |
| { |
| throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, |
| "cannot place an entry below itself" ); |
| } |
| |
| PartitionTxn partitionTxn = moveContext.getTransaction(); |
| |
| assert ( partitionTxn != null ); |
| assert ( partitionTxn instanceof PartitionWriteTxn ); |
| |
| try |
| { |
| setRWLock( moveContext ); |
| Dn oldDn = moveContext.getDn(); |
| Dn newSuperior = moveContext.getNewSuperior(); |
| Dn newDn = moveContext.getNewDn(); |
| Entry modifiedEntry = moveContext.getModifiedEntry(); |
| |
| move( partitionTxn, oldDn, newSuperior, newDn, modifiedEntry ); |
| updateCache( moveContext ); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final synchronized void move( PartitionTxn partitionTxn, Dn oldDn, Dn newSuperiorDn, Dn newDn, Entry modifiedEntry ) |
| throws LdapException |
| { |
| // Check that the parent Dn exists |
| String newParentId = getEntryId( partitionTxn, newSuperiorDn ); |
| |
| if ( newParentId == null ) |
| { |
| // This is not allowed : the parent must exist |
| throw new LdapEntryAlreadyExistsException( |
| I18n.err( I18n.ERR_256_NO_SUCH_OBJECT, newSuperiorDn.getName() ) ); |
| } |
| |
| // Now check that the new entry does not exist |
| String newId = getEntryId( partitionTxn, newDn ); |
| |
| if ( newId != null ) |
| { |
| // This is not allowed : we should not be able to move an entry |
| // to an existing position |
| throw new LdapEntryAlreadyExistsException( |
| I18n.err( I18n.ERR_250_ENTRY_ALREADY_EXISTS, newSuperiorDn.getName() ) ); |
| } |
| |
| // Get the entry and the old parent IDs |
| String entryId = getEntryId( partitionTxn, oldDn ); |
| String oldParentId = getParentId( partitionTxn, 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( partitionTxn, oldDn ); |
| |
| // Update the Rdn index |
| // First drop the old entry |
| ParentIdAndRdn movedEntry = rdnIdx.reverseLookup( partitionTxn, entryId ); |
| |
| updateRdnIdx( partitionTxn, oldParentId, REMOVE_CHILD, movedEntry.getNbDescendants() ); |
| |
| rdnIdx.drop( partitionTxn, entryId ); |
| |
| // Now, add the new entry at the right position |
| movedEntry.setParentId( newParentId ); |
| rdnIdx.add( partitionTxn, movedEntry, entryId ); |
| |
| updateRdnIdx( partitionTxn, newParentId, ADD_CHILD, movedEntry.getNbDescendants() ); |
| |
| /* |
| * 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. |
| */ |
| Dn aliasTarget = aliasIdx.reverseLookup( partitionTxn, entryId ); |
| |
| if ( null != aliasTarget ) |
| { |
| if ( !aliasTarget.isSchemaAware() ) |
| { |
| aliasTarget = new Dn( schemaManager, aliasTarget ); |
| } |
| |
| |
| addAliasIndices( partitionTxn, entryId, buildEntryDn( partitionTxn, entryId ), aliasTarget ); |
| } |
| |
| // the below case arises only when the move( Dn oldDn, Dn newSuperiorDn, Dn newDn ) is called |
| // directly using the Store API, in this case the value of modified entry will be null |
| // we need to lookup the entry to update the parent UUID |
| if ( modifiedEntry == null ) |
| { |
| modifiedEntry = fetch( partitionTxn, entryId ); |
| } |
| |
| // Update the master table with the modified entry |
| modifiedEntry.put( ApacheSchemaConstants.ENTRY_PARENT_ID_AT, newParentId ); |
| |
| // Remove the EntryDN |
| modifiedEntry.removeAttributes( entryDnAT ); |
| |
| entryDnCache.clear(); |
| |
| setContextCsn( modifiedEntry.get( entryCsnAT ).getString() ); |
| |
| master.put( partitionTxn, entryId, modifiedEntry ); |
| |
| if ( isSyncOnWrite.get() ) |
| { |
| sync(); |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // The MoveAndRename operation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException |
| { |
| if ( moveAndRenameContext.getNewSuperiorDn().isDescendantOf( moveAndRenameContext.getDn() ) ) |
| { |
| throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, |
| "cannot place an entry below itself" ); |
| } |
| |
| PartitionTxn partitionTxn = moveAndRenameContext.getTransaction(); |
| |
| assert ( partitionTxn != null ); |
| assert ( partitionTxn instanceof PartitionWriteTxn ); |
| |
| try |
| { |
| setRWLock( moveAndRenameContext ); |
| Dn oldDn = moveAndRenameContext.getDn(); |
| Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn(); |
| Rdn newRdn = moveAndRenameContext.getNewRdn(); |
| Entry modifiedEntry = moveAndRenameContext.getModifiedEntry(); |
| Map<String, List<ModDnAva>> modAvas = moveAndRenameContext.getModifiedAvas(); |
| |
| moveAndRename( partitionTxn, oldDn, newSuperiorDn, newRdn, modAvas, modifiedEntry ); |
| updateCache( moveAndRenameContext ); |
| } |
| catch ( LdapException le ) |
| { |
| // In case we get an LdapException, just rethrow it as is to |
| // avoid having it lost |
| throw le; |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| /** |
| * 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 newSuperiorDn the id of the child being moved |
| * @param newRdn the normalized dn of the new parent for the child |
| * @param modAvas The modified Avas |
| * @param modifiedEntry the modified entry |
| * @throws LdapException if something goes wrong |
| */ |
| @Override |
| public void moveAndRename( PartitionTxn partitionTxn, Dn oldDn, Dn newSuperiorDn, Rdn newRdn, Map<String, |
| List<ModDnAva>> modAvas, Entry modifiedEntry ) throws LdapException |
| { |
| // Get the child and the new parent to be entries and Ids |
| Attribute entryIdAt = modifiedEntry.get( SchemaConstants.ENTRY_UUID_AT ); |
| String entryId; |
| |
| if ( entryIdAt == null ) |
| { |
| entryId = getEntryId( partitionTxn, modifiedEntry.getDn() ); |
| } |
| else |
| { |
| entryId = modifiedEntry.get( SchemaConstants.ENTRY_UUID_AT ).getString(); |
| } |
| |
| Attribute oldParentIdAt = modifiedEntry.get( ApacheSchemaConstants.ENTRY_PARENT_ID_AT ); |
| String oldParentId; |
| |
| if ( oldParentIdAt == null ) |
| { |
| oldParentId = getEntryId( partitionTxn, oldDn.getParent() ); |
| } |
| else |
| { |
| oldParentId = oldParentIdAt.getString(); |
| } |
| |
| String newParentId = getEntryId( partitionTxn, newSuperiorDn ); |
| |
| //Get the info about the moved entry |
| ParentIdAndRdn movedEntry = rdnIdx.reverseLookup( partitionTxn, entryId ); |
| |
| // First drop the moved entry from the rdn index |
| rdnIdx.drop( partitionTxn, entryId ); |
| |
| // |
| // The update the Rdn index. We will remove the ParentIdAndRdn associated with the |
| // moved entry, and update the nbChilden of its parent and the nbSubordinates |
| // of all its ascendant, up to the common superior. |
| // Then we will add a ParentidAndRdn for the moved entry under the new superior, |
| // update its children number and the nbSubordinates of all the new ascendant. |
| updateRdnIdx( partitionTxn, oldParentId, REMOVE_CHILD, movedEntry.getNbDescendants() ); |
| |
| /* |
| * 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( partitionTxn, oldDn ); |
| |
| // Now, add the new entry at the right position |
| // First |
| movedEntry.setParentId( newParentId ); |
| movedEntry.setRdns( new Rdn[] |
| { newRdn } ); |
| rdnIdx.add( partitionTxn, movedEntry, entryId ); |
| |
| updateRdnIdx( partitionTxn, newParentId, ADD_CHILD, movedEntry.getNbDescendants() ); |
| |
| // Process the modified indexes now |
| try |
| { |
| processModifiedAvas( partitionTxn, modAvas, entryId ); |
| } |
| catch ( IndexNotFoundException infe ) |
| { |
| throw new LdapOtherException( infe.getMessage(), infe ); |
| } |
| |
| /* |
| * 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. |
| */ |
| Dn aliasTarget = aliasIdx.reverseLookup( partitionTxn, entryId ); |
| |
| if ( null != aliasTarget ) |
| { |
| if ( !aliasTarget.isSchemaAware() ) |
| { |
| aliasTarget = new Dn( schemaManager, aliasTarget ); |
| } |
| |
| addAliasIndices( partitionTxn, entryId, buildEntryDn( partitionTxn, entryId ), aliasTarget ); |
| } |
| |
| // Remove the EntryDN |
| modifiedEntry.removeAttributes( entryDnAT ); |
| |
| // Update the entryParentId attribute |
| modifiedEntry.removeAttributes( ApacheSchemaConstants.ENTRY_PARENT_ID_OID ); |
| modifiedEntry.add( ApacheSchemaConstants.ENTRY_PARENT_ID_OID, newParentId ); |
| |
| // Doom the DN cache now |
| entryDnCache.clear(); |
| |
| setContextCsn( modifiedEntry.get( entryCsnAT ).getString() ); |
| |
| // save the modified entry at the new place |
| master.put( partitionTxn, entryId, modifiedEntry ); |
| } |
| |
| |
| /** |
| * Update the index accordingly to the changed Attribute in the old and new RDN |
| * |
| * @param partitionTxn The transaction to use |
| * @param modAvs The modified AVAs |
| * @param entryId The Entry ID |
| * @throws {@link LdapException} If the AVA cannt be processed properly |
| * @throws IndexNotFoundException If teh index is not found |
| */ |
| private void processModifiedAvas( PartitionTxn partitionTxn, Map<String, List<ModDnAva>> modAvas, String entryId ) |
| throws LdapException, IndexNotFoundException |
| { |
| for ( List<ModDnAva> modDnAvas : modAvas.values() ) |
| { |
| for ( ModDnAva modDnAva : modDnAvas ) |
| { |
| AttributeType attributeType = modDnAva.getAva().getAttributeType(); |
| |
| if ( !hasIndexOn( attributeType ) ) |
| { |
| break; |
| } |
| |
| Index<?, String> index = getUserIndex( attributeType ); |
| |
| switch ( modDnAva.getType() ) |
| { |
| case ADD : |
| case UPDATE_ADD : |
| // Add Value in the index |
| ( ( Index ) index ).add( partitionTxn, modDnAva.getAva().getValue().getNormalized(), entryId ); |
| |
| /* |
| * If there is no value for id in this index due to our |
| * add above we add the entry in the presence idx |
| */ |
| if ( null == index.reverseLookup( partitionTxn, entryId ) ) |
| { |
| presenceIdx.add( partitionTxn, attributeType.getOid(), entryId ); |
| } |
| |
| break; |
| |
| case DELETE : |
| case UPDATE_DELETE : |
| ( ( Index ) index ).drop( partitionTxn, modDnAva.getAva().getValue().getNormalized(), entryId ); |
| |
| /* |
| * 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( partitionTxn, entryId ) ) |
| { |
| presenceIdx.drop( partitionTxn, attributeType.getOid(), entryId ); |
| } |
| |
| break; |
| |
| default : |
| break; |
| } |
| } |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // The Rename operation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void rename( RenameOperationContext renameContext ) throws LdapException |
| { |
| PartitionTxn partitionTxn = renameContext.getTransaction(); |
| |
| assert ( partitionTxn != null ); |
| assert ( partitionTxn instanceof PartitionWriteTxn ); |
| |
| try |
| { |
| setRWLock( renameContext ); |
| Dn oldDn = renameContext.getDn(); |
| Rdn newRdn = renameContext.getNewRdn(); |
| boolean deleteOldRdn = renameContext.getDeleteOldRdn(); |
| |
| if ( renameContext.getEntry() != null ) |
| { |
| Entry modifiedEntry = renameContext.getModifiedEntry(); |
| rename( partitionTxn, oldDn, newRdn, deleteOldRdn, modifiedEntry ); |
| } |
| else |
| { |
| rename( partitionTxn, oldDn, newRdn, deleteOldRdn, null ); |
| } |
| |
| updateCache( renameContext ); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| /** |
| * This will rename the entry, and deal with the deleteOldRdn flag. If set to true, we have |
| * to remove the AVA which are not part of the new RDN from the entry. |
| * If this flag is set to false, we have to take care of the special case of an AVA |
| * which attributeType is SINGLE-VALUE : in this case, we remove the old value. |
| */ |
| private void rename( PartitionTxn partitionTxn, String oldId, Rdn newRdn, boolean deleteOldRdn, Entry entry ) |
| throws LdapException, IndexNotFoundException |
| { |
| if ( entry == null ) |
| { |
| entry = master.get( partitionTxn, oldId ); |
| } |
| |
| Dn updn = entry.getDn(); |
| |
| if ( !newRdn.isSchemaAware() ) |
| { |
| newRdn = new Rdn( schemaManager, newRdn ); |
| } |
| |
| /* |
| * 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. |
| * Last, not least, if the AttributeType is single value, take care |
| * of removing the old value. |
| */ |
| for ( Ava newAtav : newRdn ) |
| { |
| String newNormType = newAtav.getNormType(); |
| Object newNormValue = newAtav.getValue().getString(); |
| |
| AttributeType newRdnAttrType = schemaManager.lookupAttributeTypeRegistry( newNormType ); |
| |
| if ( newRdnAttrType.isSingleValued() && entry.containsAttribute( newRdnAttrType ) ) |
| { |
| Attribute oldAttribute = entry.get( newRdnAttrType ); |
| AttributeType oldAttributeType = oldAttribute.getAttributeType(); |
| |
| // We have to remove the old attribute value, if we have some |
| entry.removeAttributes( newRdnAttrType ); |
| |
| // Deal with the index |
| if ( hasUserIndexOn( newRdnAttrType ) ) |
| { |
| Index<?, String> userIndex = getUserIndex( newRdnAttrType ); |
| |
| String normalized = oldAttributeType.getEquality().getNormalizer().normalize( oldAttribute.get().getString() ); |
| ( ( Index ) userIndex ).drop( partitionTxn, normalized, 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 == userIndex.reverseLookup( partitionTxn, oldId ) ) |
| { |
| presenceIdx.drop( partitionTxn, newRdnAttrType.getOid(), oldId ); |
| } |
| |
| } |
| } |
| |
| if ( newRdnAttrType.getSyntax().isHumanReadable() ) |
| { |
| entry.add( newRdnAttrType, newAtav.getValue().getString() ); |
| } |
| else |
| { |
| entry.add( newRdnAttrType, newAtav.getValue().getBytes() ); |
| } |
| |
| if ( hasUserIndexOn( newRdnAttrType ) ) |
| { |
| Index<?, String> userIndex = getUserIndex( newRdnAttrType ); |
| |
| /* |
| if ( oldRemoved ) |
| { |
| String normalized = newRdnAttrType.getEquality().getNormalizer().normalize( newNormValue ); |
| ( ( Index ) userIndex ).add( normalized, id ); |
| ( ( Index ) index ).drop( newNormValue, oldId ); |
| } |
| */ |
| |
| String normalized = newRdnAttrType.getEquality().getNormalizer().normalize( ( String ) newNormValue ); |
| ( ( Index ) userIndex ).add( partitionTxn, normalized, oldId ); |
| |
| |
| //( ( Index ) index ).add( newNormValue, oldId ); |
| |
| // Make sure the altered entry shows the existence of the new attrib |
| String normTypeOid = presenceNormalizer.normalize( newNormType ); |
| |
| if ( !presenceIdx.forward( partitionTxn, normTypeOid, oldId ) ) |
| { |
| presenceIdx.add( partitionTxn, normTypeOid, oldId ); |
| } |
| } |
| } |
| |
| /* |
| * 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.getValue().getString(); |
| AttributeType oldRdnAttrType = schemaManager.lookupAttributeTypeRegistry( oldNormType ); |
| entry.remove( oldRdnAttrType, oldNormValue ); |
| |
| if ( hasUserIndexOn( oldRdnAttrType ) ) |
| { |
| Index<?, String> userIndex = getUserIndex( oldRdnAttrType ); |
| |
| String normalized = oldRdnAttrType.getEquality().getNormalizer().normalize( oldNormValue ); |
| ( ( Index ) userIndex ).drop( partitionTxn, normalized, 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 == userIndex.reverseLookup( partitionTxn, oldId ) ) |
| { |
| String oldNormTypeOid = presenceNormalizer.normalize( oldNormType ); |
| presenceIdx.drop( partitionTxn, oldNormTypeOid, oldId ); |
| } |
| } |
| } |
| } |
| } |
| |
| // Remove the EntryDN |
| entry.removeAttributes( entryDnAT ); |
| |
| setContextCsn( entry.get( entryCsnAT ).getString() ); |
| |
| // And save the modified entry |
| master.put( partitionTxn, oldId, entry ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public final synchronized void rename( PartitionTxn partitionTxn, Dn dn, Rdn newRdn, boolean deleteOldRdn, Entry entry ) |
| throws LdapException |
| { |
| String oldId = getEntryId( partitionTxn, dn ); |
| |
| try |
| { |
| rename( partitionTxn, oldId, newRdn, deleteOldRdn, entry ); |
| } |
| catch ( IndexNotFoundException infe ) |
| { |
| throw new LdapOtherException( infe.getMessage(), infe ); |
| } |
| |
| /* |
| * 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. |
| */ |
| String parentId = getParentId( partitionTxn, oldId ); |
| |
| // Get the old parentIdAndRdn to get the nb of children and descendant |
| ParentIdAndRdn parentIdAndRdn = rdnIdx.reverseLookup( partitionTxn, oldId ); |
| |
| // Now we can drop it |
| rdnIdx.drop( partitionTxn, oldId ); |
| |
| // Update the descendants |
| parentIdAndRdn.setParentId( parentId ); |
| parentIdAndRdn.setRdns( newRdn ); |
| |
| rdnIdx.add( partitionTxn, parentIdAndRdn, oldId ); |
| |
| entryDnCache.clear(); |
| |
| if ( isSyncOnWrite.get() ) |
| { |
| sync(); |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // The Unbind operation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final void unbind( UnbindOperationContext unbindContext ) throws LdapException |
| { |
| // does nothing |
| } |
| |
| |
| /** |
| * This method calls {@link Partition#lookup(LookupOperationContext)} and return <tt>true</tt> |
| * if it returns an entry by default. Please override this method if |
| * there is more effective way for your implementation. |
| */ |
| @Override |
| public boolean hasEntry( HasEntryOperationContext entryContext ) throws LdapException |
| { |
| PartitionTxn partitionTxn = entryContext.getTransaction(); |
| |
| assert ( partitionTxn != null ); |
| |
| try |
| { |
| setRWLock( entryContext ); |
| |
| String id = getEntryId( partitionTxn, entryContext.getDn() ); |
| |
| Entry entry = fetch( partitionTxn, id, entryContext.getDn() ); |
| |
| return entry != null; |
| } |
| catch ( LdapException e ) |
| { |
| return false; |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // Helper methods |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * updates the CSN index |
| * |
| * @param partitionTxn The transaction to use |
| * @param entry the entry having entryCSN attribute |
| * @param id UUID of the entry |
| * @throws Exception |
| */ |
| private void updateCsnIndex( PartitionTxn partitionTxn, Entry entry, String id ) throws LdapException |
| { |
| String entryCsn = entry.get( SchemaConstants.ENTRY_CSN_AT ).getString(); |
| entryCsnIdx.drop( partitionTxn, id ); |
| entryCsnIdx.add( partitionTxn, entryCsn, id ); |
| } |
| |
| |
| // ------------------------------------------------------------------------ |
| // Index and master table Operations |
| // ------------------------------------------------------------------------ |
| /** |
| * builds the Dn of the entry identified by the given id |
| * |
| * @param partitionTxn The transaction to use |
| * @param id the entry's id |
| * @return the normalized Dn of the entry |
| * @throws LdapException If we can't build the entry Dn |
| */ |
| protected Dn buildEntryDn( PartitionTxn partitionTxn, String id ) throws LdapException |
| { |
| String parentId = id; |
| String rootId = Partition.ROOT_ID; |
| |
| // Create an array of 10 rdns, just in case. We will extend it if needed |
| Rdn[] rdnArray = new Rdn[10]; |
| int pos = 0; |
| |
| Dn dn = null; |
| |
| try |
| { |
| rwLock.readLock().lock(); |
| |
| if ( entryDnCache != null ) |
| { |
| Dn cachedDn = entryDnCache.get( id ); |
| |
| if ( cachedDn != null ) |
| { |
| return cachedDn; |
| } |
| } |
| |
| do |
| { |
| ParentIdAndRdn cur; |
| |
| if ( piarCache != null ) |
| { |
| cur = piarCache.get( parentId ); |
| |
| if ( cur == null ) |
| { |
| cur = rdnIdx.reverseLookup( partitionTxn, parentId ); |
| |
| if ( cur == null ) |
| { |
| return null; |
| } |
| |
| piarCache.put( parentId, cur ); |
| } |
| } |
| else |
| { |
| cur = rdnIdx.reverseLookup( partitionTxn, parentId ); |
| |
| if ( cur == null ) |
| { |
| return null; |
| } |
| } |
| |
| Rdn[] rdns = cur.getRdns(); |
| |
| for ( Rdn rdn : rdns ) |
| { |
| if ( ( pos > 0 ) && ( pos % 10 == 0 ) ) |
| { |
| // extend the array |
| Rdn[] newRdnArray = new Rdn[pos + 10]; |
| System.arraycopy( rdnArray, 0, newRdnArray, 0, pos ); |
| rdnArray = newRdnArray; |
| } |
| |
| rdnArray[pos++] = rdn; |
| } |
| |
| parentId = cur.getParentId(); |
| } |
| while ( !parentId.equals( rootId ) ); |
| |
| dn = new Dn( schemaManager, Arrays.copyOf( rdnArray, pos ) ); |
| |
| entryDnCache.put( id, dn ); |
| return dn; |
| } |
| finally |
| { |
| rwLock.readLock().unlock(); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public long count( PartitionTxn partitionTxn ) throws LdapException |
| { |
| return master.count( partitionTxn ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final long getChildCount( PartitionTxn partitionTxn, String id ) throws LdapException |
| { |
| try |
| { |
| ParentIdAndRdn parentIdAndRdn = rdnIdx.reverseLookup( partitionTxn, id ); |
| |
| return parentIdAndRdn.getNbChildren(); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final Dn getEntryDn( PartitionTxn partitionTxn, String id ) throws LdapException |
| { |
| return buildEntryDn( partitionTxn, id ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final String getEntryId( PartitionTxn partitionTxn, Dn dn ) throws LdapException |
| { |
| try |
| { |
| if ( Dn.isNullOrEmpty( dn ) ) |
| { |
| return Partition.ROOT_ID; |
| } |
| |
| ParentIdAndRdn suffixKey = new ParentIdAndRdn( Partition.ROOT_ID, suffixDn.getRdns() ); |
| |
| // Check into the Rdn index, starting with the partition Suffix |
| try |
| { |
| rwLock.readLock().lock(); |
| String currentId = rdnIdx.forwardLookup( partitionTxn, suffixKey ); |
| |
| for ( int i = dn.size() - suffixDn.size(); i > 0; i-- ) |
| { |
| Rdn rdn = dn.getRdn( i - 1 ); |
| ParentIdAndRdn currentRdn = new ParentIdAndRdn( currentId, rdn ); |
| |
| currentId = rdnIdx.forwardLookup( partitionTxn, currentRdn ); |
| |
| if ( currentId == null ) |
| { |
| break; |
| } |
| } |
| |
| return currentId; |
| } |
| finally |
| { |
| rwLock.readLock().unlock(); |
| } |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String getParentId( PartitionTxn partitionTxn, String childId ) throws LdapException |
| { |
| try |
| { |
| rwLock.readLock().lock(); |
| ParentIdAndRdn key = rdnIdx.reverseLookup( partitionTxn, childId ); |
| |
| if ( key == null ) |
| { |
| return null; |
| } |
| |
| return key.getParentId(); |
| } |
| finally |
| { |
| rwLock.readLock().unlock(); |
| } |
| } |
| |
| |
| /** |
| * Retrieve the SuffixID |
| * |
| * @param partitionTxn The transaction to use |
| * @return The Suffix ID |
| * @throws LdapException If we weren't able to retrieve the Suffix ID |
| */ |
| public String getSuffixId( PartitionTxn partitionTxn ) throws LdapException |
| { |
| if ( suffixId == null ) |
| { |
| ParentIdAndRdn key = new ParentIdAndRdn( Partition.ROOT_ID, suffixDn.getRdns() ); |
| |
| try |
| { |
| rwLock.readLock().lock(); |
| suffixId = rdnIdx.forwardLookup( partitionTxn, key ); |
| } |
| finally |
| { |
| rwLock.readLock().unlock(); |
| } |
| } |
| |
| return suffixId; |
| } |
| |
| |
| //------------------------------------------------------------------------ |
| // Index handling |
| //------------------------------------------------------------------------ |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addIndex( Index<?, String> index ) throws LdapException |
| { |
| checkInitialized( "addIndex" ); |
| |
| // Check that the index String is valid |
| AttributeType attributeType = null; |
| |
| try |
| { |
| attributeType = schemaManager.lookupAttributeTypeRegistry( index.getAttributeId() ); |
| } |
| catch ( LdapNoSuchAttributeException lnsae ) |
| { |
| LOG.error( "Cannot initialize the index for AttributeType {}, this value does not exist", |
| index.getAttributeId() ); |
| |
| return; |
| } |
| |
| String oid = attributeType.getOid(); |
| |
| if ( SYS_INDEX_OIDS.contains( oid ) ) |
| { |
| if ( !systemIndices.containsKey( oid ) ) |
| { |
| systemIndices.put( oid, index ); |
| } |
| } |
| else |
| { |
| if ( !userIndices.containsKey( oid ) ) |
| { |
| userIndices.put( oid, index ); |
| } |
| } |
| } |
| |
| |
| /** |
| * Add some new indexes |
| * @param indexes The added indexes |
| */ |
| public void addIndexedAttributes( Index<?, String>... indexes ) |
| { |
| for ( Index<?, String> index : indexes ) |
| { |
| indexedAttributes.add( index ); |
| } |
| } |
| |
| |
| /** |
| * Set the list of indexes for this partition |
| * @param indexedAttributes The list of indexes |
| */ |
| public void setIndexedAttributes( Set<Index<?, String>> indexedAttributes ) |
| { |
| this.indexedAttributes = indexedAttributes; |
| } |
| |
| |
| /** |
| * @return The list of indexed attributes |
| */ |
| public Set<Index<?, String>> getIndexedAttributes() |
| { |
| return indexedAttributes; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Iterator<String> getUserIndices() |
| { |
| return userIndices.keySet().iterator(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Iterator<String> getSystemIndices() |
| { |
| return systemIndices.keySet().iterator(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Index<?, String> getIndex( AttributeType 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} |
| */ |
| @Override |
| public Index<?, String> getUserIndex( AttributeType attributeType ) throws IndexNotFoundException |
| { |
| if ( attributeType == null ) |
| { |
| throw new IndexNotFoundException( I18n.err( I18n.ERR_3, attributeType, attributeType ) ); |
| } |
| |
| String oid = attributeType.getOid(); |
| |
| if ( userIndices.containsKey( oid ) ) |
| { |
| return userIndices.get( oid ); |
| } |
| |
| throw new IndexNotFoundException( I18n.err( I18n.ERR_3, attributeType, attributeType ) ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Index<?, String> getSystemIndex( AttributeType attributeType ) throws IndexNotFoundException |
| { |
| if ( attributeType == null ) |
| { |
| throw new IndexNotFoundException( I18n.err( I18n.ERR_2, attributeType, attributeType ) ); |
| } |
| |
| String oid = attributeType.getOid(); |
| |
| if ( systemIndices.containsKey( oid ) ) |
| { |
| return systemIndices.get( oid ); |
| } |
| |
| throw new IndexNotFoundException( I18n.err( I18n.ERR_2, attributeType, attributeType ) ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public Index<Dn, String> getAliasIndex() |
| { |
| return ( Index<Dn, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_ALIAS_AT_OID ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public Index<String, String> getOneAliasIndex() |
| { |
| return ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_ONE_ALIAS_AT_OID ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public Index<String, String> getSubAliasIndex() |
| { |
| return ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_SUB_ALIAS_AT_OID ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public Index<String, String> getObjectClassIndex() |
| { |
| return ( Index<String, String> ) systemIndices.get( SchemaConstants.OBJECT_CLASS_AT_OID ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public Index<String, String> getEntryCsnIndex() |
| { |
| return ( Index<String, String> ) systemIndices.get( SchemaConstants.ENTRY_CSN_AT_OID ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @SuppressWarnings("unchecked") |
| public Index<String, String> getAdministrativeRoleIndex() |
| { |
| return ( Index<String, String> ) systemIndices.get( SchemaConstants.ADMINISTRATIVE_ROLE_AT_OID ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public Index<String, String> getPresenceIndex() |
| { |
| return ( Index<String, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_PRESENCE_AT_OID ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public Index<ParentIdAndRdn, String> getRdnIndex() |
| { |
| return ( Index<ParentIdAndRdn, String> ) systemIndices.get( ApacheSchemaConstants.APACHE_RDN_AT_OID ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean hasUserIndexOn( AttributeType attributeType ) throws LdapException |
| { |
| String oid = attributeType.getOid(); |
| return userIndices.containsKey( oid ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean hasSystemIndexOn( AttributeType attributeType ) throws LdapException |
| { |
| return systemIndices.containsKey( attributeType.getOid() ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean hasIndexOn( AttributeType attributeType ) throws LdapException |
| { |
| return hasUserIndexOn( attributeType ) || hasSystemIndexOn( attributeType ); |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // Alias index manipulation |
| //--------------------------------------------------------------------------------------------- |
| /** |
| * Adds userIndices for an aliasEntry to be added to the database while checking |
| * for constrained alias constructs like alias cycles and chaining. |
| * |
| * @param partitionTxn The transaction to use |
| * @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 LdapException if the wrappedCursor btrees cannot be altered |
| */ |
| protected void addAliasIndices( PartitionTxn partitionTxn, String aliasId, Dn aliasDn, Dn aliasTarget ) |
| throws LdapException |
| { |
| String targetId; // Id of the aliasedObjectName |
| Dn ancestorDn; // Name of an alias entry relative |
| String ancestorId; // Id of an alias entry relative |
| |
| /* |
| * 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 ( !aliasTarget.isDescendantOf( suffixDn ) ) |
| { |
| String msg = I18n.err( I18n.ERR_225, suffixDn.getName() ); |
| throw new LdapAliasDereferencingException( msg ); |
| } |
| |
| // L O O K U P T A R G E T I D |
| targetId = getEntryId( partitionTxn, aliasTarget ); |
| |
| /* |
| * 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 ); |
| throw new LdapAliasException( msg ); |
| } |
| |
| /* |
| * 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( partitionTxn, targetId ) ) |
| { |
| String msg = I18n.err( I18n.ERR_227 ); |
| throw new LdapAliasDereferencingException( msg ); |
| } |
| |
| // Add the alias to the simple alias index |
| aliasIdx.add( partitionTxn, aliasTarget, aliasId ); |
| |
| if ( aliasCache != null ) |
| { |
| aliasCache.put( aliasId, aliasTarget ); |
| } |
| |
| /* |
| * 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( partitionTxn, ancestorDn ); |
| |
| // check if alias parent and aliased entry are the same |
| Dn normalizedAliasTargetParentDn = aliasTarget.getParent(); |
| |
| if ( !aliasDn.isDescendantOf( normalizedAliasTargetParentDn ) ) |
| { |
| oneAliasIdx.add( partitionTxn, 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 ( !aliasTarget.isDescendantOf( ancestorDn ) ) |
| { |
| subAliasIdx.add( partitionTxn, ancestorId, targetId ); |
| } |
| |
| ancestorDn = ancestorDn.getParent(); |
| ancestorId = getEntryId( partitionTxn, 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 partitionTxn The transaction to use |
| * @param aliasId the id of the alias entry in the master table |
| * @throws LdapException if we cannot delete index values in the database |
| */ |
| protected void dropAliasIndices( PartitionTxn partitionTxn, String aliasId ) throws LdapException |
| { |
| Dn targetDn = aliasIdx.reverseLookup( partitionTxn, aliasId ); |
| |
| if ( !targetDn.isSchemaAware() ) |
| { |
| targetDn = new Dn( schemaManager, targetDn ); |
| } |
| |
| String targetId = getEntryId( partitionTxn, 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( partitionTxn, aliasId ); |
| |
| Dn ancestorDn = aliasDn.getParent(); |
| String ancestorId = getEntryId( partitionTxn, 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( partitionTxn, ancestorId, targetId ); |
| subAliasIdx.drop( partitionTxn, ancestorId, targetId ); |
| |
| while ( !ancestorDn.equals( suffixDn ) && ancestorDn.size() > suffixDn.size() ) |
| { |
| ancestorDn = ancestorDn.getParent(); |
| ancestorId = getEntryId( partitionTxn, ancestorDn ); |
| |
| subAliasIdx.drop( partitionTxn, ancestorId, targetId ); |
| } |
| |
| // Drops all alias tuples pointing to the id of the alias to be deleted |
| aliasIdx.drop( partitionTxn, aliasId ); |
| |
| if ( aliasCache != null ) |
| { |
| aliasCache.remove( 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 partitionTxn The transaction to use |
| * @param movedBase the base at which the move occurred - the moved node |
| * @throws LdapException if system userIndices fail |
| */ |
| protected void dropMovedAliasIndices( PartitionTxn partitionTxn, Dn movedBase ) throws LdapException |
| { |
| String movedBaseId = getEntryId( partitionTxn, movedBase ); |
| |
| Dn targetDn = aliasIdx.reverseLookup( partitionTxn, movedBaseId ); |
| |
| if ( targetDn != null ) |
| { |
| if ( !targetDn.isSchemaAware() ) |
| { |
| targetDn = new Dn( schemaManager, targetDn ); |
| } |
| |
| String targetId = getEntryId( partitionTxn, targetDn ); |
| Dn aliasDn = getEntryDn( partitionTxn, movedBaseId ); |
| |
| /* |
| * Start droping index tuples with the first ancestor right above the |
| * moved base. This is the first ancestor effected by the move. |
| */ |
| Dn ancestorDn = movedBase.getParent(); |
| String ancestorId = getEntryId( partitionTxn, 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( partitionTxn, ancestorId, targetId ); |
| } |
| |
| subAliasIdx.drop( partitionTxn, ancestorId, targetId ); |
| |
| while ( !ancestorDn.equals( suffixDn ) ) |
| { |
| ancestorDn = ancestorDn.getParent(); |
| ancestorId = getEntryId( partitionTxn, ancestorDn ); |
| |
| subAliasIdx.drop( partitionTxn, ancestorId, targetId ); |
| } |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------------------------- |
| // Debug methods |
| //--------------------------------------------------------------------------------------------- |
| private void dumpIndex( PartitionTxn partitionTxn, OutputStream stream, Index<?, String> index ) |
| { |
| try |
| { |
| Cursor<IndexEntry<?, String>> cursor = ( Cursor ) index.forwardCursor( partitionTxn ); |
| |
| while ( cursor.next() ) |
| { |
| IndexEntry<?, String> entry = cursor.get(); |
| |
| System.out.println( entry ); |
| } |
| } |
| catch ( Exception e ) |
| { |
| // TODO : fixme |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void dumpIndex( PartitionTxn partitionTxn, OutputStream stream, String name ) throws IOException |
| { |
| try |
| { |
| AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( name ); |
| |
| if ( attributeType == null ) |
| { |
| stream.write( Strings.getBytesUtf8( "Cannot find an index for AttributeType names " + name ) ); |
| |
| return; |
| } |
| |
| if ( attributeType.getOid().equals( ApacheSchemaConstants.APACHE_RDN_AT_OID ) ) |
| { |
| dumpIndex( partitionTxn, stream, rdnIdx ); |
| } |
| } |
| catch ( LdapException le ) |
| { |
| stream.write( Strings.getBytesUtf8( "Cannot find an index for AttributeType names " + name ) ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String toString() |
| { |
| return "Partition<" + id + ">"; |
| } |
| |
| |
| /** |
| * Create a new Index for a given OID |
| * |
| * @param indexOid The Attribute OID |
| * @param path The working directory where this index will be stored |
| * @param withReverse If the Reverse index must be created or not |
| * @return The created index |
| * @throws LdapException If the index can't be created |
| */ |
| protected abstract Index createSystemIndex( String indexOid, URI path, boolean withReverse ) throws LdapException; |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public MasterTable getMasterTable() |
| { |
| return master; |
| } |
| |
| |
| /** |
| * Acquire a Read lock |
| */ |
| private void lockRead() |
| { |
| rwLock.readLock().lock(); |
| } |
| |
| |
| /** |
| * Release a Read lock |
| */ |
| private void unlockRead() |
| { |
| rwLock.readLock().unlock(); |
| } |
| |
| |
| /** |
| * Acquire a Write lock |
| */ |
| private void lockWrite() |
| { |
| rwLock.writeLock().lock(); |
| } |
| |
| |
| /** |
| * Release a Write lock |
| */ |
| private void unlockWrite() |
| { |
| rwLock.writeLock().unlock(); |
| } |
| |
| |
| /** |
| * updates the cache based on the type of OperationContext |
| * |
| * @param opCtx the operation's context |
| */ |
| public void updateCache( OperationContext opCtx ) |
| { |
| // partition implementations should override this if they want to use cache |
| } |
| |
| |
| /** |
| * looks up for the entry with the given ID in the cache |
| * |
| * @param id the ID of the entry |
| * @return the Entry if exists, null otherwise |
| */ |
| public Entry lookupCache( String id ) |
| { |
| return null; |
| } |
| |
| |
| /** |
| * adds the given entry to cache |
| * |
| * Note: this method is not called during add operation to avoid filling the cache |
| * with all the added entries |
| * |
| * @param id ID of the entry |
| * @param entry the Entry |
| */ |
| public void addToCache( String id, Entry entry ) |
| { |
| } |
| |
| |
| /** |
| * @return the optimizer |
| */ |
| public Optimizer getOptimizer() |
| { |
| return optimizer; |
| } |
| |
| |
| /** |
| * @param optimizer the optimizer to set |
| */ |
| public void setOptimizer( Optimizer optimizer ) |
| { |
| this.optimizer = optimizer; |
| } |
| |
| |
| /** |
| * @param searchEngine the searchEngine to set |
| */ |
| public void setSearchEngine( SearchEngine searchEngine ) |
| { |
| this.searchEngine = searchEngine; |
| } |
| |
| |
| /** |
| * Set and return the ReadWrite lock we use to protect the backend against concurrent modifications |
| * |
| * @param operationContext The OperationContext which contain the reference to the OperationManager |
| */ |
| private void setRWLock( OperationContext operationContext ) |
| { |
| if ( operationContext.getSession() != null ) |
| { |
| rwLock = operationContext.getSession().getDirectoryService().getOperationManager().getRWLock(); |
| } |
| else |
| { |
| if ( rwLock == null ) |
| { |
| // Create a ReadWrite lock from scratch |
| rwLock = new ReentrantReadWriteLock(); |
| } |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ReadWriteLock getReadWriteLock() |
| { |
| return rwLock; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Cache<String, Dn> getAliasCache() |
| { |
| return aliasCache; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String getContextCsn( PartitionTxn partitionTxn ) |
| { |
| if ( super.getContextCsn( partitionTxn ) == null ) |
| { |
| loadContextCsn( partitionTxn ); |
| } |
| |
| return super.getContextCsn( partitionTxn ); |
| } |
| |
| |
| /** |
| * Loads the current context CSN present in the context entry of the partition |
| * |
| * @param partitionTxn The transaction to use |
| */ |
| protected void loadContextCsn( PartitionTxn partitionTxn ) |
| { |
| try |
| { |
| if ( rwLock == null ) |
| { |
| // Create a ReadWrite lock from scratch |
| rwLock = new ReentrantReadWriteLock(); |
| } |
| |
| // load the last stored valid CSN value |
| String contextEntryId = getEntryId( partitionTxn, getSuffixDn() ); |
| |
| if ( contextEntryId == null ) |
| { |
| return; |
| } |
| |
| Entry entry = fetch( partitionTxn, contextEntryId ); |
| |
| Attribute ctxCsnAt = entry.get( contextCsnAT ); |
| |
| if ( ctxCsnAt != null ) |
| { |
| setContextCsn( ctxCsnAt.getString() ); |
| ctxCsnChanged = false; // this is just loaded, not new |
| } |
| } |
| catch ( LdapException e ) |
| { |
| throw new RuntimeException( e ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| // store the contextCSN value in the context entry |
| // note that this modification shouldn't change the entryCSN value of the context entry |
| @Override |
| public void saveContextCsn( PartitionTxn partitionTxn ) throws LdapException |
| { |
| if ( !ctxCsnChanged ) |
| { |
| return; |
| } |
| |
| String contextCsn = super.getContextCsn( partitionTxn ); |
| |
| if ( contextCsn == null ) |
| { |
| return; |
| } |
| |
| try |
| { |
| // we don't need to use the ctxCsnSemaphore here cause |
| // the only other place this is called is from PartitionNexus.sync() |
| // but that is protected by write lock in DefaultDirectoryService.shutdown() |
| |
| String contextEntryId = getEntryId( partitionTxn, getSuffixDn() ); |
| Entry origEntry = fetch( partitionTxn, contextEntryId ); |
| |
| // The Context Entry may have been deleted. Get out if we don't find it |
| if ( origEntry == null ) |
| { |
| return; |
| } |
| |
| origEntry = ( ( ClonedServerEntry ) origEntry ).getOriginalEntry(); |
| |
| origEntry.removeAttributes( contextCsnAT, entryDnAT ); |
| |
| origEntry.add( contextCsnAT, contextCsn ); |
| |
| master.put( partitionTxn, contextEntryId, origEntry ); |
| |
| ctxCsnChanged = false; |
| |
| LOG.debug( "Saved context CSN {} for the partition {}", contextCsn, suffixDn ); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationErrorException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Subordinates getSubordinates( PartitionTxn partitionTxn, Entry entry ) throws LdapException |
| { |
| Subordinates subordinates = new Subordinates(); |
| |
| try |
| { |
| // Check into the Rdn index, starting with the partition Suffix |
| try |
| { |
| rwLock.readLock().lock(); |
| ParentIdAndRdn parentIdAndRdn = rdnIdx.reverseLookup( partitionTxn, entry.get( SchemaConstants.ENTRY_UUID_AT ).getString() ); |
| |
| subordinates.setNbChildren( parentIdAndRdn.getNbChildren() ); |
| subordinates.setNbSubordinates( parentIdAndRdn.getNbDescendants() ); |
| } |
| finally |
| { |
| rwLock.readLock().unlock(); |
| } |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapException( e.getMessage(), e ); |
| } |
| |
| return subordinates; |
| } |
| } |