EntryContainer.java revision 23cb1d1297a0807fe827a38a17c54bfe1fd05e2d
/*
* 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 to which this entry container belongs. */
/** The root container in which this entryContainer belongs. */
private final RootContainer rootContainer;
/** The baseDN this entry container is responsible for. */
/** The backend configuration. */
private PluggableBackendCfg config;
/** 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 ID2Count 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. */
private final HashMap<AttributeType, AttributeIndex> attrIndexMap = new HashMap<AttributeType, AttributeIndex>();
/** 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
{
/** {@inheritDoc} */
public boolean isConfigurationAddAcceptable(final BackendIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
{
try
{
// FIXME this should be a read operation, but I cannot change it
// because of AttributeIndex ctor.
{
{
//Try creating all the indexes before confirming they are valid ones.
}
});
return true;
}
catch(Exception e)
{
return false;
}
}
/** {@inheritDoc} */
{
try
{
{
{
{
ccr.setAdminActionRequired(true);
}
}
});
}
catch(Exception e)
{
}
return ccr;
}
/** {@inheritDoc} */
public boolean isConfigurationDeleteAcceptable(
{
// TODO: validate more before returning true?
return true;
}
/** {@inheritDoc} */
{
try
{
{
{
}
});
}
{
}
finally
{
}
return ccr;
}
}
/**
* This class is responsible for managing the configuration for VLV indexes
* used within this entry container.
*/
private class VLVIndexCfgManager implements
{
/** {@inheritDoc} */
public boolean isConfigurationAddAcceptable(
{
try
{
}
catch(Exception e)
{
return false;
}
{
try
{
{
ascending[i] = false;
}
else
{
ascending[i] = true;
{
}
}
}
catch(Exception e)
{
return false;
}
{
return false;
}
}
return true;
}
/** {@inheritDoc} */
{
try
{
{
{
{
ccr.setAdminActionRequired(true);
}
}
});
}
catch(Exception e)
{
}
return ccr;
}
/** {@inheritDoc} */
public boolean isConfigurationDeleteAcceptable(BackendVLVIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
{
// TODO: validate more before returning true?
return true;
}
/** {@inheritDoc} */
{
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 backend A reference to 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
* @throws StorageRuntimeException If an error occurs in the storage.
* @throws ConfigException if a configuration related error occurs.
*/
{
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.
*/
{
}
/**
* Return attribute index map.
*
* @return The attribute index map.
*/
{
return attrIndexMap;
}
/**
* Look for an 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.
*/
{
try
{
// Position a cursor on the last data item, and the key should give the highest ID.
if (cursor.positionToLastKey())
{
}
return new EntryID(0);
}
finally
{
}
}
{
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)
{
null));
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))
{
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)
{
null));
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)
throws DirectoryException
{
final EntryIDSet scopeSet;
try
{
switch (searchScope.asEnum())
{
case BASE_OBJECT:
{
}
break;
case SINGLE_LEVEL:
{
}
break;
case SUBORDINATES:
case WHOLE_SUBTREE:
{
}
break;
default:
}
}
catch (NoSuchElementException e)
{
}
return scopeSet;
}
});
}
catch (Exception e)
{
}
}
private static EntryIDSet newIDSetFromCursor(SequentialCursor<?, EntryID> cursor, boolean includeCurrent)
{
final long ids[] = new long[SCOPE_IDSET_LIMIT];
int offset = 0;
if (includeCurrent) {
}
}
return offset == SCOPE_IDSET_LIMIT
}
{
{
}
else if (e instanceof StorageRuntimeException)
{
throw (StorageRuntimeException) e;
}
throw new StorageRuntimeException(e);
}
private <E1 extends Exception, E2 extends Exception> void throwIfPossible(final Throwable cause, Class<E1> clazz1,
{
{
}
{
}
}
private boolean processSearchWithVirtualAttributeRule(final SearchOperation searchOperation, boolean isPreIndexed)
{
{
{
return true;
}
}
return false;
}
{
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;
try
{
try
{
// 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.
}
}
finally
{
}
}
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.
*/
{
try
{
{
{
try
{
// Check whether the entry already exists.
{
}
// Check that the parent entry exists.
{
// Check for referral entries above the target.
// Read the parent ID from dn2id.
{
}
}
// Insert into the indexes, in index configuration order.
if (addOperation != null)
{
// One last check before committing
addOperation.checkIfCanceled(true);
}
// Update the entry cache.
if (entryCache != null)
{
}
}
{
throw StorageRuntimeException;
}
catch (DirectoryException directoryException)
{
throw directoryException;
}
catch (CanceledOperationException coe)
{
throw coe;
}
catch (Exception e)
{
{
}
throw new DirectoryException(
}
}
});
}
catch (Exception 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
{
{
{
try
{
// Check for referral entries above the target entry.
// Determine whether this is a subtree delete.
boolean isSubtreeDelete =
/*
* 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.
*/
int subordinateEntriesDeleted = 0;
try
{
// Step forward until we pass the ending value.
{
// We have found a subordinate entry.
if (!isSubtreeDelete)
{
// The subtree delete control was not specified and
// the target entry is not a leaf.
}
/*
* Delete this entry which by now must be a leaf because we have
* been deleting from the bottom of the tree upwards.
*/
// Invoke any subordinate delete plugins on the entry.
{
if (!pluginResult.continueProcessing())
{
}
}
if (deleteOperation != null)
{
deleteOperation.checkIfCanceled(false);
}
// Get the next DN.
}
}
finally
{
}
// 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.
if (deleteOperation != null)
{
// One last check before committing
deleteOperation.checkIfCanceled(true);
}
if (isSubtreeDelete)
{
subordinateEntriesDeleted + 1));
}
}
{
throw StorageRuntimeException;
}
catch (DirectoryException directoryException)
{
throw directoryException;
}
catch (CanceledOperationException coe)
{
throw coe;
}
catch (Exception e)
{
{
}
throw new DirectoryException(
}
}
});
}
catch (Exception e)
{
}
}
boolean manageDsaIT,
{
{
// Read the entry ID from dn2id.
{
}
// FIXME: previously this used a RMW lock - see OPENDJ-1878.
{
}
}
// Remove from dn2id.
{
// Do not expect to ever come through here.
throw new DirectoryException(
ResultCode.NO_SUCH_OBJECT, ERR_DELETE_NO_SUCH_OBJECT.get(leafDNKey), getMatchedDN(txn, baseDN), null);
}
// Check that the entry exists in id2entry and read its contents.
// FIXME: previously this used a RMW lock - see OPENDJ-1878.
{
}
if (!manageDsaIT)
{
}
// Update the referral tree.
// Remove from id2entry.
{
}
// Remove from the indexes, in index config order.
// Remove the children counter for this entry.
// Iterate up through the superior entries from the target entry.
{
{
}
}
// Remove the entry from the entry cache.
if (entryCache != null)
{
}
}
/**
* 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.
*
* @throws DirectoryException If a problem occurs while trying to make the
* determination.
*/
{
// Try the entry cache first.
{
return true;
}
}
{
{
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
{
{
{
}
});
}
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;
}
}
try
{
{
// The entryDN does not exist. Check for referral entries above the target 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;
}
catch (Exception e)
{
// it is not very clean to specify twice the same exception but it saves me some code for now
return null; // unreachable
}
}
/**
* 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
{
{
{
try
{
{
}
{
// Check if the entry is a referral entry.
}
// Update the referral tree.
if (modifyOperation != null)
{
// In this case we know from the operation what the modifications were.
}
else
{
}
// Replace id2entry.
// 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 StorageRuntimeException;
}
catch (DirectoryException directoryException)
{
throw directoryException;
}
catch (CanceledOperationException coe)
{
throw coe;
}
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 currentDN The current DN of the entry to be replaced.
* @param entry 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.
*/
{
// FIXME: consistency + isolation cannot be maintained lock free - see OPENDJ-1878.
try
{
{
{
final boolean isApexEntryMoved;
if (oldSuperiorDN != null)
{
}
else if (newSuperiorDN != null)
{
}
else
{
isApexEntryMoved = false;
}
try
{
// Check whether the renamed entry already exists.
{
}
{
// Check for referral entries above the target entry.
}
if (oldApexEntry == null)
{
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), ERR_MISSING_ID2ENTRY_RECORD
}
{
}
{
/*
* 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.
*/
if (newSuperiorID == null)
{
}
{
// This move would break the above invariant so we must
// renumber every entry that moves. This is even more
// expensive since every entry has to be deleted from
// and added back into the attribute indexes.
if (logger.isTraceEnabled())
{
+ "Old DN: %s " + "New DN: %s " + "Old entry ID: %d " + "New entry ID: %d "
}
}
}
// Move or rename the apex entry.
removeApexEntry(txn, buffer, oldSuperiorDN, oldApexID, newApexID, oldApexEntry, entry, isApexEntryMoved,
/*
* 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.
*/
try
{
// Step forward until we pass the ending value.
{
// We have found a subordinate entry.
// Construct the new DN of the entry.
// Assign a new entry ID if we are renumbering.
{
if (logger.isTraceEnabled())
{
+ "Old DN: %s New DN: %s Old entry ID: %d New entry ID: %d",
}
}
// Move this entry.
if (modifyDNOperation != null)
{
modifyDNOperation.checkIfCanceled(false);
}
// Get the next DN.
}
}
finally
{
}
// Set current to the first moved entry and null out the head.
// This will allow processed moved entries to be GCed.
{
}
if (modifyDNOperation != null)
{
// One last check before committing
modifyDNOperation.checkIfCanceled(true);
}
}
{
throw StorageRuntimeException;
}
catch (DirectoryException directoryException)
{
throw directoryException;
}
catch (CanceledOperationException coe)
{
throw coe;
}
catch (Exception e)
{
{
}
throw new DirectoryException(
}
}
});
}
catch (Exception e)
{
}
}
/** Represents an renamed entry that was deleted from but yet to be added back. */
private static final class MovedEntry
{
private MovedEntry next;
private boolean renumbered;
{
this.renumbered = renumbered;
}
}
boolean isApexEntryMoved,
boolean renumbered,
{
// FIXME: the core server should validate that the new subtree location is empty.
{
// Reindex the entry with the new ID.
}
if(isApexEntryMoved)
{
{
}
}
}
boolean isApexEntryMoved,
{
// Remove the old DN from dn2id.
// Remove old ID from id2entry and put the new entry
// (old entry with new DN) in id2entry.
{
}
// Update any referral records.
{
// Since entry has moved, oldSuperiorDN has lost a child
}
{
}
{
// Reindex the entry with the new ID.
}
else
{
// Update the indexes if needed.
}
// Remove the entry from the entry cache.
if (entryCache != null)
{
}
}
boolean isApexEntryMoved,
{
// 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())
{
{
}
}
}
// Remove the old DN from dn2id.
// Remove old ID from id2entry and put the new entry
// (old entry with new DN) in id2entry.
{
}
// Update any referral records.
{
// Reindex the entry with the new ID.
}
else if (!modifications.isEmpty())
{
// Update the indexes.
}
// Remove the entry from the entry cache.
if (entryCache != null)
{
}
}
/**
* 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
*
* @param txn
* a non null transaction
* @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
{
{
{
}
});
}
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 can not 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;
}
/**
* Sets a new tree prefix for this entry container and rename all
* existing trees in use by this entry container.
*
* @param newBaseDN The new tree prefix to use.
* @throws StorageRuntimeException If an error occurs in the storage.
*/
{
try
{
// Rename in transaction.
{
{
{
}
}
});
// Only rename the containers if the txn succeeded.
{
}
}
catch (Exception e)
{
{
}
}
finally
{
try
{
{
{
// Open the containers backup.
{
}
}
});
}
catch (Exception e)
{
{
}
}
}
}
/** {@inheritDoc} */
{
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;
}
}
/** {@inheritDoc} */
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;
}
/** {@inheritDoc} */
{
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);
}
}
{
try
{
{
}
}
finally
{
{
}
{
{
}
}
}
}
{
{
}
return allTrees;
}
/**
* Clear the contents for a tree from disk.
*
* @param txn a non null transaction
* @param tree The tree to clear.
* @throws StorageRuntimeException if a storage error occurs.
*/
{
try
{
}
finally
{
}
if(logger.isTraceEnabled())
{
}
}
/**
* Finds an existing entry whose DN is the closest ancestor of a given baseDN.
*
* @param baseDN 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.
*/
{
boolean attributeModified = false;
{
{
attributeModified = true;
break;
}
{
{
attributeModified = true;
break;
}
}
}
return attributeModified;
}
/**
* Fetch the base Entry of the EntryContainer.
* @param baseDN 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
{
try
{
}
catch (Exception e)
{
logger.traceException(e);
}
// The base entry must exist for a successful result.
{
// 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()
{
}
/** {@inheritDoc} */
return treePrefix;
}
}