EntryContainer.java revision 870930ab008b106540456b3efe47e7ec613c174d
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
* Portions copyright 2013 Manuel Gaupp
*/
/**
* Storage container for LDAP entries. Each base DN of a backend is given
* its own entry container. The entry container is the object that implements
* the guts of the backend API methods for LDAP operations.
*/
public class EntryContainer
{
/** Number of EntryID to considers when building EntryIDSet from DN2ID. */
private static final int SCOPE_IDSET_LIMIT = 4096;
/** The name of the entry tree. */
/** The name of the DN tree. */
/** The name of the children index tree. */
/** The name of the referral tree. */
/** The name of the state tree. */
/** The attribute index configuration manager. */
private final AttributeIndexCfgManager attributeIndexCfgManager;
/** The vlv index configuration manager. */
private final VLVIndexCfgManager vlvIndexCfgManager;
/** The backend configuration. */
private PluggableBackendCfg config;
/** ID of the backend to which this entry container belongs. */
/** The baseDN this entry container is responsible for. */
/** The root container in which this entryContainer belongs. */
private final RootContainer rootContainer;
/** The tree storage. */
/** The DN tree maps a normalized DN string to an entry ID (8 bytes). */
/** The entry tree maps an entry ID (8 bytes) to a complete encoded entry. */
/** Store the number of children for each entry. */
private final ID2ChildrenCount id2childrenCount;
/** The referral tree maps a normalized DN string to labeled URIs. */
/** The state tree maps a config DN to config entries. */
/** The set of attribute indexes. */
/** The set of VLV (Virtual List View) indexes. */
/**
* Prevents name clashes for common indexes (like id2entry) across multiple suffixes.
* For example when a root container contains multiple suffixes.
*/
private final String treePrefix;
/**
* This class is responsible for managing the configuration for attribute
* indexes used within this entry container.
*/
private class AttributeIndexCfgManager implements
{
public boolean isConfigurationAddAcceptable(final BackendIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
{
try
{
return true;
}
catch(Exception e)
{
return false;
}
}
{
try
{
{
{
{
ccr.setAdminActionRequired(true);
}
}
});
}
catch(Exception e)
{
}
return ccr;
}
public boolean isConfigurationDeleteAcceptable(
{
// TODO: validate more before returning true?
return true;
}
{
try
{
{
{
}
});
}
{
}
finally
{
}
return ccr;
}
}
/**
* This class is responsible for managing the configuration for VLV indexes
* used within this entry container.
*/
private class VLVIndexCfgManager implements
{
public boolean isConfigurationAddAcceptable(BackendVLVIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
{
try
{
}
catch(Exception e)
{
return false;
}
{
try
{
{
}
}
catch(Exception e)
{
return false;
}
{
return false;
}
}
return true;
}
{
try
{
{
{
{
ccr.setAdminActionRequired(true);
}
}
});
}
catch(Exception e)
{
}
return ccr;
}
public boolean isConfigurationDeleteAcceptable(BackendVLVIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
{
// TODO: validate more before returning true?
return true;
}
{
try
{
{
{
}
});
}
catch (Exception e)
{
}
finally
{
}
return ccr;
}
}
/** A read write lock to handle schema changes and bulk changes. */
/**
* Create a new entry container object.
*
* @param baseDN The baseDN this entry container will be responsible for
* storing on disk.
* @param backendID ID of the backend that is creating this entry container.
* It is needed by the Directory Server entry cache methods.
* @param config The configuration of the backend.
* @param storage The storage for this entryContainer.
* @param rootContainer The root container this entry container is in.
* @throws ConfigException if a configuration related error occurs.
*/
{
this.rootContainer = rootContainer;
config.addPluggableChangeListener(this);
vlvIndexCfgManager = new VLVIndexCfgManager();
}
{
}
/**
* Opens the entryContainer for reading and writing.
*
* @param txn a non null transaction
* @param accessMode specifies how the container has to be opened (read-write or read-only)
* @throws StorageRuntimeException If an error occurs in the storage.
* @throws ConfigException if a configuration related error occurs.
*/
void open(WriteableTransaction txn, AccessMode accessMode) throws StorageRuntimeException, ConfigException
{
try
{
{
{
}
}
{
{
}
}
}
catch (StorageRuntimeException de)
{
close();
throw de;
}
}
/**
* Closes the entry container.
*
* @throws StorageRuntimeException If an error occurs in the storage.
*/
public void close() throws StorageRuntimeException
{
// Deregister any listeners.
}
/**
* Retrieves a reference to the root container in which this entry container
* exists.
*
* @return A reference to the root container in which this entry container
* exists.
*/
{
return rootContainer;
}
/**
* Get the DN tree used by this entry container.
* The entryContainer must have been opened.
*
* @return The DN tree.
*/
{
return dn2id;
}
/**
* Get the entry tree used by this entry container.
* The entryContainer must have been opened.
*
* @return The entry tree.
*/
{
return id2entry;
}
/**
* Get the referral tree used by this entry container.
* The entryContainer must have been opened.
*
* @return The referral tree.
*/
{
return dn2uri;
}
/**
* Get the children tree used by this entry container.
* The entryContainer must have been opened.
*
* @return The children tree.
*/
{
return id2childrenCount;
}
/**
* Look for an attribute index for the given attribute type.
*
* @param attrType The attribute type for which an attribute index is needed.
* @return The attribute index or null if there is none for that type.
*/
{
}
/**
* Look for a VLV index for the given index name.
*
* @param vlvIndexName The vlv index name for which an vlv index is needed.
* @return The VLV index or null if there is none with that name.
*/
{
}
/**
* Retrieve all attribute indexes.
*
* @return All attribute indexes defined in this entry container.
*/
{
return attrIndexMap.values();
}
/**
* Retrieve all VLV indexes.
*
* @return The collection of VLV indexes defined in this entry container.
*/
{
return vlvIndexMap.values();
}
/**
* Determine the highest entryID in the entryContainer.
* The entryContainer must already be open.
*
* @param txn a non null transaction
* @return The highest entry ID.
* @throws StorageRuntimeException If an error occurs in the storage.
*/
{
{
// Position a cursor on the last data item, and the key should give the highest ID.
if (cursor.positionToLastKey())
{
}
return new EntryID(0);
}
}
{
try
{
{
{
{
}
}
});
}
catch (Exception e)
{
throw new StorageRuntimeException(e);
}
}
/**
* Determine the number of children entries for a given entry.
*
* @param entryDN The distinguished name of the entry.
* @return The number of children entries for the given entry or -1 if
* the entry does not exist.
* @throws StorageRuntimeException If an error occurs in the storage.
*/
{
try
{
{
{
}
});
}
catch (Exception e)
{
throw new StorageRuntimeException(e);
}
}
/**
* Processes the specified search in this entryContainer.
* Matching entries should be provided back to the core server using the
* <CODE>SearchOperation.returnEntry</CODE> method.
*
* @param searchOperation The search operation to be processed.
* @throws DirectoryException
* If a problem occurs while processing the
* search.
* @throws StorageRuntimeException If an error occurs in the storage.
* @throws CanceledOperationException if this operation should be cancelled.
*/
{
try
{
{
{
{
/*
* If the control's criticality field is true then the server SHOULD
* do the following: return unavailableCriticalExtension as a return
* code in the searchResultDone message; include the
* sortKeyResponseControl in the searchResultDone message, and not
* send back any search result entries.
*/
return null;
}
{
throw new DirectoryException(
}
// Handle client abandon of paged results.
if (pageRequest != null)
{
{
return null;
}
{
// The RFC says : "If the page size is greater than or equal to the
// sizeLimit value, the server should ignore the control as the
// request can be satisfied in a single page"
pageRequest = null;
}
}
// Handle base-object search first.
{
{
}
{
}
if (pageRequest != null)
{
// Indicate no more pages.
}
return null;
}
// Check whether the client requested debug information about the
// contribution of the indexes to the search.
{
debugBuffer = new StringBuilder();
}
boolean candidatesAreInScope = false;
if (sortRequest != null)
{
{
try
{
if (entryIDSet != null)
{
candidatesAreInScope = true;
break;
}
}
catch (DirectoryException de)
{
if (sortRequest.isCritical())
{
throw de;
}
}
}
}
if (entryIDSet == null)
{
if (processSearchWithVirtualAttributeRule(searchOperation, true))
{
return null;
}
// Create an index filter to get the search result candidate entries
// Evaluate the filter against the attribute indexes.
if (!isBelowFilterThreshold(entryIDSet))
{
final int idSetLimit =
if (debugBuffer != null)
{
}
{
// In this case we know that every candidate is in scope.
candidatesAreInScope = true;
}
}
if (sortRequest != null)
{
try
{
// If the sort key is not present, the sorting will generate the
// default ordering. VLV search request goes through as if
// this sort key was not found in the user entry.
if (sortRequest.containsSortKeys())
{
}
else
{
/*
* There is no sort key associated with the sort control.
* Since it came here it means that the criticality is false
* so let the server return all search results unsorted and
* include the sortKeyResponseControl in the searchResultDone
* message.
*/
}
}
catch (DirectoryException de)
{
if (sortRequest.isCritical())
{
throw de;
}
}
}
}
// If requested, construct and return a fictitious entry containing
// debug information, and no other entries.
if (debugBuffer != null)
{
return null;
}
if (entryIDSet.isDefined())
{
}
else
{
if (processSearchWithVirtualAttributeRule(searchOperation, false))
{
return null;
}
{
throw new DirectoryException(
}
if (sortRequest != null)
{
// FIXME -- Add support for sorting unindexed searches using indexes
// like DSEE currently does.
if (sortRequest.isCritical())
{
throw new DirectoryException(
}
}
}
return null;
}
{
}
private EntryIDSet getIDSetFromScope(final ReadableTransaction txn, DN aBaseDN, SearchScope searchScope,
int idSetLimit) throws DirectoryException
{
final EntryIDSet scopeSet;
try
{
switch (searchScope.asEnum())
{
case BASE_OBJECT:
{
}
break;
case SINGLE_LEVEL:
{
}
break;
case SUBORDINATES:
case WHOLE_SUBTREE:
{
scopeSet = newIDSetFromCursor(scopeCursor, searchScope.equals(SearchScope.WHOLE_SUBTREE), idSetLimit);
}
break;
default:
}
}
catch (NoSuchElementException e)
{
}
return scopeSet;
}
});
}
catch (Exception e)
{
}
}
private static EntryIDSet newIDSetFromCursor(SequentialCursor<?, EntryID> cursor, boolean includeCurrent,
int idSetLimit)
{
long entryIDs[] = new long[idSetLimit];
int offset = 0;
if (includeCurrent)
{
}
{
}
{
return EntryIDSet.newUndefinedSet();
}
else if (offset != idSetLimit)
{
}
}
{
{
}
else if (e instanceof StorageRuntimeException)
{
throw (StorageRuntimeException) e;
}
throw new StorageRuntimeException(e);
}
private static <E1 extends Exception, E2 extends Exception> void throwIfPossible(final Throwable cause,
{
{
}
{
}
}
boolean isPreIndexed)
{
{
{
return true;
}
}
return false;
}
private static Entry buildDebugSearchIndexEntry(StringBuilder debugBuffer) throws DirectoryException
{
return entry;
}
/**
* We were not able to obtain a set of candidate entry IDs for the
* search from the indexes.
* <p>
* Here we are relying on the DN key order to ensure children are
* returned after their parents.
* <ul>
* <li>iterate through a subtree range of the DN tree
* <li>discard non-children DNs if the search scope is single level
* <li>fetch the entry by ID from the entry cache or the entry tree
* <li>return the entry if it matches the filter
* </ul>
*
* @param searchOperation The search operation.
* @param pageRequest A Paged Results control, or null if none.
* @throws DirectoryException If an error prevented the search from being
* processed.
*/
{
// The base entry must already have been processed if this is
// a request for the next page in paged results. So we skip
// the base entry processing if the cookie is set.
{
if (!manageDsaIT)
{
}
/* The base entry is only included for whole subtree search. */
{
}
if (!manageDsaIT
&& pageRequest != null)
{
// Indicate no more pages.
}
}
/*
* We will iterate forwards through a range of the dn2id keys to
* find subordinates of the target entry from the top of the tree
* downwards. For example, any subordinates of "dc=example,dc=com" appear
* in dn2id with a key ending in ",dc=example,dc=com". The entry
* "cn=joe,ou=people,dc=example,dc=com" will appear after the entry
* "ou=people,dc=example,dc=com".
*/
// Set the starting value.
{
// The cookie contains the DN of the next entry to be returned.
try
{
}
catch (Exception e)
{
logger.traceException(e);
}
}
else
{
// Set the starting value to the suffix.
}
int lookthroughCount = 0;
{
// Initialize the cursor very close to the starting value.
// Step forward until we pass the ending value.
{
{
// Lookthrough limit exceeded
return;
}
// We have found a subordinate entry.
boolean isInScope =
// Check if this entry is an immediate child.
if (isInScope)
{
// Process the candidate entry.
{
{
if (pageRequest != null
{
// The current page is full.
// Set the cookie to remember where we were.
return;
}
{
// We have been told to discontinue processing of the
// search. This could be due to size limit exceeded or
// operation cancelled.
return;
}
}
}
}
searchOperation.checkIfCanceled(false);
// Move to the next record.
}
}
catch (StorageRuntimeException e)
{
logger.traceException(e);
}
if (pageRequest != null)
{
// Indicate no more pages.
}
}
/**
* Returns the entry corresponding to the provided entryID.
*
* @param txn a non null transaction
* @param entryID
* the id of the entry to retrieve
* @return the entry corresponding to the provided entryID
* @throws DirectoryException
* If an error occurs retrieving the entry
*/
{
// Try the entry cache first.
if (cacheEntry != null)
{
return cacheEntry;
}
{
// Put the entry in the cache making sure not to overwrite a newer copy
// that may have been inserted since the time we read the cache.
}
return entry;
}
/**
* We were able to obtain a set of candidate entry IDs for the
* search from the indexes.
* <p>
* Here we are relying on ID order to ensure children are returned
* after their parents.
* <ul>
* <li>Iterate through the candidate IDs
* <li>fetch entry by ID from cache or id2entry
* <li>put the entry in the cache if not present
* <li>discard entries that are not in scope
* <li>return entry if it matches the filter
* </ul>
*
* @param entryIDSet The candidate entry IDs.
* @param candidatesAreInScope true if it is certain that every candidate
* entry is in the search scope.
* @param searchOperation The search operation.
* @param pageRequest A Paged Results control, or null if none.
* @throws DirectoryException If an error prevented the search from being
* processed.
*/
private void searchIndexed(ReadableTransaction txn, EntryIDSet entryIDSet, boolean candidatesAreInScope,
{
boolean continueSearch = true;
// Set the starting value.
{
// The cookie contains the ID of the next entry to be returned.
try
{
}
catch (Exception e)
{
logger.traceException(e);
}
}
else if (!manageDsaIT)
{
}
// Make sure the candidate list is smaller than the lookthrough limit
int lookthroughLimit =
{
//Lookthrough limit exceeded
continueSearch = false;
}
// Iterate through the index candidates.
if (continueSearch)
{
{
try
{
}
catch (Exception e)
{
logger.traceException(e);
continue;
}
// Process the candidate entry.
{
if (pageRequest != null
{
// The current page is full.
// Set the cookie to remember where we were.
return;
}
{
// We have been told to discontinue processing of the
// search. This could be due to size limit exceeded or
// operation cancelled.
break;
}
}
}
searchOperation.checkIfCanceled(false);
}
// Before we return success from the search we must ensure the base entry
// exists. However, if we have returned at least one entry or subordinate
// reference it implies the base does exist, so we can omit the check.
{
if (!manageDsaIT)
{
}
}
if (pageRequest != null)
{
// Indicate no more pages.
}
}
private boolean isInScope(boolean candidatesAreInScope, SearchScope searchScope, DN aBaseDN, Entry entry)
{
if (candidatesAreInScope)
{
return true;
}
{
// Check if this entry is an immediate child.
{
return true;
}
}
{
{
return true;
}
}
{
return true;
}
return false;
}
/**
* Adds the provided entry to this tree. This method must ensure that the
* entry is appropriate for the tree and that no entry already exists with
* the same DN. The caller must hold a write lock on the DN of the provided
* entry.
*
* @param entry The entry to add to this tree.
* @param addOperation The add operation with which the new entry is
* associated. This may be <CODE>null</CODE> for adds
* performed internally.
* @throws DirectoryException If a problem occurs while trying to add the
* entry.
* @throws StorageRuntimeException If an error occurs in the storage.
* @throws CanceledOperationException if this operation should be cancelled.
*/
{
// Insert into the indexes, in index configuration order.
try
{
{
{
// No need to call indexBuffer.reset() since IndexBuffer content will be the same for each retry attempt.
try
{
// Check whether the entry already exists.
{
}
// Check that the parent entry exists.
{
// Check for referral entries above the target.
{
null);
}
}
// Ensure same access ordering as deleteEntry.
if (addOperation != null)
{
// One last check before committing
addOperation.checkIfCanceled(true);
}
}
{
throw e;
}
catch (Exception e)
{
{
}
throw new DirectoryException(
}
}
});
}
catch (Exception e)
{
}
if (entryCache != null)
{
}
}
{
// Transaction modifying the index has been rolled back.
// Ensure that the index trusted state is persisted.
try
{
{
{
}
});
}
catch (Exception e)
{
// Cannot throw because this method is used in a catch block and we do not want to hide the real exception.
logger.traceException(e);
}
}
{
}
/**
* Removes the specified entry from this tree. This method must ensure
* that the entry exists and that it does not have any subordinate entries
* (unless the storage supports a subtree delete operation and the client
* included the appropriate information in the request). The caller must hold
* a write lock on the provided entry DN.
*
* @param entryDN The DN of the entry to remove from this tree.
* @param deleteOperation The delete operation with which this action is
* associated. This may be <CODE>null</CODE> for
* deletes performed internally.
* @throws DirectoryException If a problem occurs while trying to remove the
* entry.
* @throws StorageRuntimeException If an error occurs in the storage.
* @throws CanceledOperationException if this operation should be cancelled.
*/
{
try
{
{
{
indexBuffer.reset();
try
{
// Check for referral entries above the target entry.
// We'll need the parent ID when we update the id2childrenCount. Fetch it now so that accesses to dn2id
// are ordered.
{
{
null);
}
}
// Delete the subordinate entries in dn2id if requested.
/* draft-armijo-ldap-treedelete, 4.1 Tree Delete Semantics: The server MUST NOT chase referrals stored in
* the tree. If information about referrals is stored in this section of the tree, this pointer will be
* deleted.
*/
/* Ensure that all index updates are done in the correct order to avoid deadlocks. First iterate over
* dn2id collecting all the IDs of the entries to be deleted. Then update dn2uri, id2entry,
* id2childrenCount, and finally the attribute indexes.
*/
{
// Delete the target entry in dn2id.
{
null);
}
// Now delete the subordinate entries in dn2id.
{
if (!isSubtreeDelete)
{
}
checkIfCanceled(false);
}
}
// The target entry will have the lowest entryID so it will remain the first element.
// Now update id2entry, dn2uri, and id2childrenCount in key order.
boolean isBaseEntry = true;
{
{
{
}
if (isBaseEntry && !isManageDsaIT)
{
}
if (!isBaseEntry)
{
}
if (entryCache != null)
{
}
isBaseEntry = false;
checkIfCanceled(false);
}
}
checkIfCanceled(true);
if (isSubtreeDelete)
{
entriesToBeDeleted.size()));
}
}
{
throw e;
}
catch (Exception e)
{
{
}
throw new DirectoryException(
}
}
{
{
if (!pluginResult.continueProcessing())
{
}
}
}
{
if (deleteOperation != null)
{
}
}
});
}
catch (Exception e)
{
}
}
/**
* Indicates whether an entry with the specified DN exists.
*
* @param entryDN The DN of the entry for which to determine existence.
*
* @return <CODE>true</CODE> if the specified entry exists,
* or <CODE>false</CODE> if it does not.
*/
{
// Try the entry cache first.
}
{
{
return true;
}
try
{
{
{
}
});
}
catch (Exception e)
{
throw new StorageRuntimeException(e);
}
}
/**
* Fetch an entry by DN, trying the entry cache first, then the tree.
* Retrieves the requested entry, trying the entry cache first,
* then the tree.
*
* @param entryDN The distinguished name of the entry to retrieve.
* @return The requested entry, or <CODE>null</CODE> if the entry does not
* exist.
* @throws DirectoryException If a problem occurs while trying to retrieve
* the entry.
* @throws StorageRuntimeException An error occurred during a storage operation.
*/
{
try
{
{
{
{
// The entryDN does not exist. Check for referral entries above the target entry.
}
return entry;
}
});
}
catch (Exception e)
{
// it is not very clean to specify twice the same exception but it saves me some code for now
return null; // it can never happen
}
}
private Entry getEntry0(ReadableTransaction txn, final DN entryDN) throws StorageRuntimeException, DirectoryException
{
if (entryCache != null)
{
{
return entry;
}
}
{
return null;
}
{
/*
* Put the entry in the cache making sure not to overwrite a newer copy that may have been
* inserted since the time we read the cache.
*/
}
return entry;
}
/**
* The simplest case of replacing an entry in which the entry DN has
* not changed.
*
* @param oldEntry The old contents of the entry
* @param newEntry The new contents of the entry
* @param modifyOperation The modify operation with which this action is
* associated. This may be <CODE>null</CODE> for
* modifications performed internally.
* @throws StorageRuntimeException If an error occurs in the storage.
* @throws DirectoryException If a Directory Server error occurs.
* @throws CanceledOperationException if this operation should be cancelled.
*/
void replaceEntry(final Entry oldEntry, final Entry newEntry, final ModifyOperation modifyOperation)
{
try
{
{
{
indexBuffer.reset();
try
{
{
null);
}
{
// Check if the entry is a referral entry.
}
// Ensure same ordering as deleteEntry: id2entry, dn2uri, then indexes.
// Update the referral tree.
if (modifyOperation != null)
{
// In this case we know from the operation what the modifications were.
}
else
{
}
// Update the indexes.
if (modifyOperation != null)
{
// In this case we know from the operation what the modifications were.
}
else
{
// The most optimal would be to figure out what the modifications were.
}
if(modifyOperation != null)
{
// One last check before committing
modifyOperation.checkIfCanceled(true);
}
// Update the entry cache.
if (entryCache != null)
{
}
}
{
throw e;
}
catch (Exception e)
{
{
}
throw new DirectoryException(
}
}
});
}
catch (Exception e)
{
}
}
/**
* subordinate entries as necessary. This must ensure that an entry already
* exists with the provided current DN, and that no entry exists with the
* target DN of the provided entry. The caller must hold write locks on both
* the current DN and the new DN for the entry.
*
* @param oldTargetDN The current DN of the entry to be renamed.
* @param newTargetEntry The new content to use for the entry.
* @param modifyDNOperation The modify DN operation with which this action
* is associated. This may be <CODE>null</CODE>
* for modify DN operations performed internally.
* @throws DirectoryException
* If a problem occurs while trying to perform the rename.
* @throws CanceledOperationException
* If this backend noticed and reacted
* to a request to cancel or abandon the
* modify DN operation.
* @throws StorageRuntimeException If an error occurs in the storage.
*/
void renameEntry(final DN oldTargetDN, final Entry newTargetEntry, final ModifyDNOperation modifyDNOperation)
{
try
{
{
{
indexBuffer.reset();
try
{
// Validate the request.
{
// Check for referral entries above the target entry.
null);
}
{
null);
}
{
}
/* We want to preserve the invariant that the ID of an entry is greater than its parent, since search
* results are returned in ID order. Note: if the superior has changed then oldSuperiorDN and
* newSuperiorDN will be non-null.
*/
/* Ensure that all index updates are done in the correct order to avoid deadlocks. First iterate over
* dn2id collecting all the IDs of the entries to be renamed. Then update dn2uri, id2entry,
* id2childrenCount, and finally the attribute indexes.
*/
// The target entry will have the lowest entryID so it will remain the first element.
// Now update id2entry, dn2uri, and id2childrenCount in key order.
if (superiorHasChanged)
{
}
boolean isBaseEntry = true;
{
{
renameSingleEntry(txn, renamedEntryID, cursor, indexBuffer, newTargetDN, renumberEntryIDs, isBaseEntry);
isBaseEntry = false;
checkIfCanceled(false);
}
}
checkIfCanceled(true);
}
{
throw e;
}
catch (Exception e)
{
{
}
throw new DirectoryException(
}
}
private void renameSingleEntry(
final WriteableTransaction txn,
final IndexBuffer indexBuffer,
final DN newTargetDN,
final boolean renumberEntryIDs,
final boolean isBaseEntry) throws DirectoryException
{
{
}
if (isBaseEntry)
{
{
}
}
else
{
}
if (renumberEntryIDs)
{
}
if (renumberEntryIDs)
{
// In-order: new entryID is guaranteed to be greater than old entryID.
}
{
// Slow path: the entry has been renumbered so we need to fully re-index.
}
else if (!modifications.isEmpty())
{
// Fast-path: the entryID has not changed so we only need to re-index the mods.
}
if (entryCache != null)
{
}
}
{
if (modifyDNOperation != null)
{
}
}
{
final List<Modification> modifications = Collections.unmodifiableList(new ArrayList<Modification>(0));
// Create a new entry that is a copy of the old entry but with the new DN.
// Also invoke any subordinate modify DN plugins on the entry.
// FIXME -- At the present time, we don't support subordinate modify DN
// plugins that make changes to subordinate entries and therefore
// provide an unmodifiable list for the modifications element.
// FIXME -- This will need to be updated appropriately if we decided that
// these plugins should be invoked for synchronization operations.
{
if (!pluginResult.continueProcessing())
{
}
if (!modifications.isEmpty())
{
{
}
}
}
return modifications;
}
});
}
catch (Exception e)
{
}
}
/**
* Make a new DN for a subordinate entry of a renamed or moved entry.
*
* @param oldDN The current DN of the subordinate entry.
* @param oldSuffixLen The current DN length of the renamed or moved entry.
* @param newSuffixDN The new DN of the renamed or moved entry.
* @return The new DN of the subordinate entry.
*/
{
for (int i=0; i < oldDNKeepComponents; i++)
{
}
{
}
return new DN(newDNComponents);
}
/**
* Insert a new entry into the attribute indexes.
*
* @param buffer The index buffer used to buffer up the index changes.
* @param entry The entry to be inserted into the indexes.
* @param entryID The ID of the entry to be inserted into the indexes.
* @throws StorageRuntimeException If an error occurs in the storage.
* @throws DirectoryException If a Directory Server error occurs.
*/
{
{
}
{
}
}
/**
* Remove an entry from the attribute indexes.
*
* @param buffer The index buffer used to buffer up the index changes.
* @param entry The entry to be removed from the indexes.
* @param entryID The ID of the entry to be removed from the indexes.
* @throws StorageRuntimeException If an error occurs in the storage.
* @throws DirectoryException If a Directory Server error occurs.
*/
{
{
}
{
}
}
/**
* Update the attribute indexes to reflect the changes to the
* attributes of an entry resulting from a sequence of modifications.
*
* @param buffer The index buffer used to buffer up the index changes.
* @param oldEntry The contents of the entry before the change.
* @param newEntry The contents of the entry after the change.
* @param entryID The ID of the entry that was changed.
* @param mods The sequence of modifications made to the entry.
* @throws StorageRuntimeException If an error occurs in the storage.
* @throws DirectoryException If a Directory Server error occurs.
*/
{
// Process in index configuration order.
{
// Check whether any modifications apply to this indexed attribute.
{
}
}
{
}
}
/**
* Get a count of the number of entries stored in this entry container including the baseDN
*
* @return The number of entries stored in this entry container including the baseDN.
* @throws StorageRuntimeException
* If an error occurs in the storage.
*/
long getNumberOfEntriesInBaseDN() throws StorageRuntimeException
{
try
{
{
{
return getNumberOfEntriesInBaseDN0(txn);
}
});
}
catch (Exception e)
{
throw new StorageRuntimeException(e);
}
}
{
}
/**
* Determine whether the provided operation has the ManageDsaIT request control.
* @param operation The operation for which the determination is to be made.
* @return true if the operation has the ManageDsaIT request control, or false if not.
*/
{
{
{
{
{
return true;
}
}
}
}
return false;
}
/**
* Delete this entry container from disk. The entry container should be
* closed before calling this method.
*
* @param txn a non null transaction
* @throws StorageRuntimeException If an error occurs while removing the entry container.
*/
{
{
}
}
/**
* Remove a tree from disk.
*
* @param txn a non null transaction
* @param tree The tree container to remove.
* @throws StorageRuntimeException If an error occurs while attempting to delete the tree.
*/
{
{
// The state tree cannot be removed individually.
return;
}
{
}
}
/**
* This method constructs a container name from a base DN. Only alphanumeric
* characters are preserved, all other characters are replaced with an
* underscore.
*
* @return The container name for the base DN.
*/
{
return treePrefix;
}
{
return baseDN;
}
/**
* Get the parent of a DN in the scope of the base DN.
*
* @param dn A DN which is in the scope of the base DN.
* @return The parent DN, or null if the given DN is the base DN.
*/
{
{
return null;
}
}
public boolean isConfigurationChangeAcceptable(
{
// This is always true because only all config attributes used
// by the entry container should be validated by the admin framework.
return true;
}
{
try
{
{
{
}
});
}
catch (Exception e)
{
}
finally
{
}
return ccr;
}
/**
* Clear the contents of this entry container.
*
* @throws StorageRuntimeException If an error occurs while removing the entry
* container.
*/
public void clear() throws StorageRuntimeException
{
try
{
{
{
{
{
}
}
}
});
}
catch (Exception e)
{
throw new StorageRuntimeException(e);
}
}
{
{
}
return allTrees;
}
/**
* Finds an existing entry whose DN is the closest ancestor of a given baseDN.
*
* @param targetDN the DN for which we are searching a matched DN.
* @return the DN of the closest ancestor of the baseDN.
* @throws DirectoryException If an error prevented the check of an
* existing entry from being performed.
*/
{
{
{
return parentDN;
}
}
return null;
}
/**
* Checks if any modifications apply to this indexed attribute.
* @param index the indexed attributes.
* @param mods the modifications to check for.
* @return true if any apply, false otherwise.
*/
{
{
{
return true;
}
}
return false;
}
/**
* Fetch the base Entry of the EntryContainer.
* @param searchBaseDN the DN for the base entry
* @param searchScope the scope under which this is fetched.
* Scope is used for referral processing.
* @return the Entry matching the baseDN.
* @throws DirectoryException if the baseDN doesn't exist.
*/
throws DirectoryException
{
{
// Check for referral entries above the base entry.
}
return baseEntry;
}
private EntryIDSet sort(ReadableTransaction txn, EntryIDSet entryIDSet, SearchOperation searchOperation,
{
if (!entryIDSet.isDefined())
{
return newUndefinedSet();
}
{
try
{
{
}
}
catch (Exception e)
{
LocalizableMessage message = ERR_ENTRYIDSORTER_CANNOT_EXAMINE_ENTRY.get(id, getExceptionMessage(e));
}
}
// See if there is a VLV request to further pare down the set of results, and if there is where it should be
// processed by offset or assertion value.
if (vlvRequest == null)
{
}
{
}
}
{
int i = 0;
{
}
return array;
}
throws DirectoryException
{
boolean targetFound = false;
int index = 0;
int targetIndex = 0;
int startIndex = 0;
int includedAfterCount = 0;
{
if (targetFound)
{
{
break;
}
}
else
{
if (targetFound)
{
}
targetIndex++;
}
}
final EntryIDSet result;
if (targetFound)
{
}
else
{
/*
* No entry was found to be greater than or equal to the sort key, so the target offset will
* be one greater than the content count.
*/
result = newDefinedSet();
}
searchOperation.addResponseControl(new VLVResponseControl(targetIndex, sortMap.size(), LDAPResultCode.SUCCESS));
return result;
}
private static final EntryIDSet sortByOffset(SearchOperation searchOperation, VLVRequestControl vlvRequest,
{
if (targetOffset < 0)
{
// The client specified a negative target offset. This
// should never be allowed.
}
// This is an easy mistake to make, since VLV offsets start at 1 instead of 0. We'll assume the client meant
// to use 1.
if (startPos < 0)
{
// This can happen if beforeCount >= offset, and in this case we'll just adjust the start position to ignore
// the range of beforeCount that doesn't exist.
startPos = 0;
}
{
// The start position is beyond the end of the list. In this case, we'll assume that the start position was
// one greater than the size of the list and will only return the beforeCount entries.
afterCount = 0;
}
int treePos = 0;
int arrayPos = 0;
{
{
continue;
}
{
break;
}
}
{
// We don't have enough entries in the set to meet the requested page size, so we'll need to shorten the
// array.
}
searchOperation.addResponseControl(new VLVResponseControl(targetOffset, sortMap.size(), LDAPResultCode.SUCCESS));
return newDefinedSet(sortedIDs);
}
/** Get the exclusive lock. */
void lock()
{
}
/** Unlock the exclusive lock. */
void unlock()
{
}
return treePrefix;
}
}