EntryContainer.java revision 9f0904fda87bfcf921deeccdbaeafe834fbad696
/*
* 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 JE 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
{
/** The name of the entry database. */
/** The name of the DN database. */
/** The name of the children index database. */
/** The name of the subtree index database. */
/** The name of the referral database. */
/** The name of the state database. */
/** The attribute index configuration manager. */
private final AttributeJEIndexCfgManager attributeJEIndexCfgManager;
/** The vlv index configuration manager. */
private final VLVJEIndexCfgManager vlvJEIndexCfgManager;
/** 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 LocalDBBackendCfg config;
/** The JE database environment. */
private final Environment env;
/** The DN database maps a normalized DN string to an entry ID (8 bytes). */
/** The entry database maps an entry ID (8 bytes) to a complete encoded entry. */
/** Index maps entry ID to an entry ID list containing its children. */
private Index id2children;
/** Index maps entry ID to an entry ID list containing its subordinates. */
private Index id2subtree;
/** The referral database maps a normalized DN string to labeled URIs. */
/** The state database 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 String databasePrefix;
/**
* This class is responsible for managing the configuration for attribute
* indexes used within this entry container.
*/
private class AttributeJEIndexCfgManager implements
{
/** {@inheritDoc} */
public boolean isConfigurationAddAcceptable(
{
try
{
//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
{
}
catch(DatabaseException de)
{
}
finally
{
}
return ccr;
}
}
/**
* This class is responsible for managing the configuration for VLV indexes
* used within this entry container.
*/
private class VLVJEIndexCfgManager implements
{
/** {@inheritDoc} */
public boolean isConfigurationAddAcceptable(
{
try
{
}
catch(Exception e)
{
e.getLocalizedMessage());
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(
{
// TODO: validate more before returning true?
return true;
}
/** {@inheritDoc} */
{
try
{
}
catch(DatabaseException de)
{
}
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 databasePrefix The prefix to use in the database names used by
* this entry container.
* @param backend A reference to the JE backend that is creating this entry
* container. It is needed by the Directory Server entry cache
* methods.
* @param config The configuration of the JE backend.
* @param env The JE environment to create this entryContainer in.
* @param rootContainer The root container this entry container is in.
* @throws ConfigException if a configuration related error occurs.
*/
throws ConfigException
{
this.rootContainer = rootContainer;
config.addLocalDBChangeListener(this);
vlvJEIndexCfgManager = new VLVJEIndexCfgManager();
}
/**
* Opens the entryContainer for reading and writing.
*
* @throws DatabaseException If an error occurs in the JE database.
* @throws ConfigException if a configuration related error occurs.
*/
{
try
{
entryDataConfig, env, this);
{
}
else
{
// Use a null index and ensure that future attempts to use the real
// subordinate indexes will fail.
{
}
{
}
}
{
{
}
}
{
{
}
}
}
catch (DatabaseException de)
{
close();
throw de;
}
}
/**
* Closes the entry container.
*
* @throws DatabaseException If an error occurs in the JE database.
*/
public void close() throws DatabaseException
{
// Close core indexes.
id2children.close();
id2subtree.close();
{
}
// 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.
*/
public RootContainer getRootContainer()
{
return rootContainer;
}
/**
* Get the DN database used by this entry container.
* The entryContainer must have been opened.
*
* @return The DN database.
*/
{
return dn2id;
}
/**
* Get the entry database used by this entry container.
* The entryContainer must have been opened.
*
* @return The entry database.
*/
public ID2Entry getID2Entry()
{
return id2entry;
}
/**
* Get the referral database used by this entry container.
* The entryContainer must have been opened.
*
* @return The referral database.
*/
{
return dn2uri;
}
/**
* Get the children database used by this entry container.
* The entryContainer must have been opened.
*
* @return The children database.
*/
public Index getID2Children()
{
return id2children;
}
/**
* Get the subtree database used by this entry container.
* The entryContainer must have been opened.
*
* @return The subtree database.
*/
public Index getID2Subtree()
{
return id2subtree;
}
/**
* Get the state database used by this entry container.
* The entry container must have been opened.
*
* @return The state database.
*/
{
return state;
}
/**
* 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.
*
* @return The highest entry ID.
* @throws DatabaseException If an error occurs in the JE database.
*/
{
try
{
// Position a cursor on the last data item, and the key should give the highest ID.
{
}
return new EntryID(0);
}
finally
{
}
}
/**
* Determine the number of subordinate entries for a given entry.
*
* @param entryDN The distinguished name of the entry.
* @param subtree <code>true</code> will include the entry and all the
* entries under the given entries. <code>false</code>
* will only return the number of entries immediately
* under the given entry.
* @return The number of subordinate entries for the given entry or -1 if
* the entry does not exist.
* @throws DatabaseException If an error occurs in the JE database.
*/
throws DatabaseException
{
{
final EntryIDSet entryIDSet;
long count;
if (subtree)
{
}
else
{
count = 0;
}
{
return -1;
}
}
return -1;
}
/**
* 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 DatabaseException If an error occurs in the JE database.
* @throws CanceledOperationException if this operation should be cancelled.
*/
{
&& sortRequest.isCritical())
{
/*
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;
}
{
}
// Handle client abandon of paged results.
if (pageRequest != null)
{
{
return;
}
{
// 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.
{
// Fetch the base entry.
{
}
{
}
if (pageRequest != null)
{
// Indicate no more pages.
}
return;
}
// 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(entryIDList != null)
{
candidatesAreInScope = true;
break;
}
}
catch (DirectoryException de)
{
searchOperation.addResponseControl(new ServerSideSortResponseControl(de.getResultCode().intValue(), null));
if (sortRequest.isCritical())
{
throw de;
}
}
}
}
if(entryIDList == null)
{
// See if we could use a virtual attribute rule to process the search.
{
{
return;
}
}
// Create an index filter to get the search result candidate entries.
// Evaluate the filter against the attribute indexes.
// Evaluate the search scope against the id2children and id2subtree
// indexes.
{
// Read the ID from dn2id.
{
}
{
}
else
{
{
// The id2subtree list does not include the base entry ID.
}
}
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)
{
searchOperation.addResponseControl(new ServerSideSortResponseControl(de.getResultCode().intValue(), 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;
}
if (entryIDList.isDefined())
{
{
}
}
else
{
{
}
// See if we could use a virtual attribute rule to process the search.
{
{
return;
}
}
{
}
if (sortRequest != null)
{
// FIXME -- Add support for sorting unindexed searches using indexes
// like DSEE currently does.
if (sortRequest.isCritical())
{
}
}
}
}
/**
* 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 database
* <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 database
* <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.
{
// Fetch the base entry.
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".
*/
final byte special = 0x00;
/*
* Set the ending value to a value of equal length but slightly
* greater than the suffix. Since keys are compared in
* reverse order we must set the first byte (the comma).
* No possibility of overflow here.
*/
// Set the starting value.
byte[] begin;
{
// 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;
}
if (cmp >= 0)
{
// We have gone past the ending value.
break;
}
// 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 (DatabaseException e)
{
logger.traceException(e);
}
if (pageRequest != null)
{
// Indicate no more pages.
}
}
/**
* Returns the entry corresponding to the provided entryID.
*
* @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 entryIDList 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.
*/
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);
msg, e);
}
}
else if (!manageDsaIT)
{
// Return any search result references.
}
// 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.
{
// Filter the entry if it is in scope.
{
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.
{
// Fetch the base entry if it exists.
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 database. This method must ensure that the
* entry is appropriate for the database 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 database.
* @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 DatabaseException If an error occurs in the JE database.
* @throws CanceledOperationException if this operation should be cancelled.
*/
{
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 dn2id.
{
// Do not ever expect to come through here.
}
// Update the referral database for referral entries.
{
// Do not ever expect to come through here.
}
// Insert into id2entry.
{
// Do not ever expect to come through here.
}
// Insert into the indexes, in index configuration order.
// Insert into id2children and id2subtree.
// The database transaction locks on these records will be hotly
// contested so we do them last so as to hold the locks for the
// shortest duration.
{
// Iterate up through the superior entries, starting above the parent.
{
// Read the ID from dn2id.
{
}
// Insert into id2subtree for this node.
}
}
if(addOperation != null)
{
// One last check before committing
addOperation.checkIfCanceled(true);
}
// Commit the transaction.
// Update the entry cache.
if (entryCache != null)
{
}
}
catch (DatabaseException databaseException)
{
throw databaseException;
}
catch (DirectoryException directoryException)
{
throw directoryException;
}
catch (CanceledOperationException coe)
{
throw coe;
}
catch (Exception e)
{
{
}
message, e);
}
}
{
}
/**
* Removes the specified entry from this database. This method must ensure
* that the entry exists and that it does not have any subordinate entries
* (unless the database 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 database.
* @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 DatabaseException If an error occurs in the JE database.
* @throws CanceledOperationException if this operation should be cancelled.
*/
{
try
{
// Check for referral entries above the target entry.
// Determine whether this is a subtree delete.
/*
* 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.
*/
byte special = 0x00;
/*
* Set the ending value to a value of equal length but slightly
* greater than the suffix.
*/
int subordinateEntriesDeleted = 0;
cursorConfig.setReadCommitted(true);
try
{
// Initialize the cursor very close to the starting value.
// Step forward until the key is greater than the starting value.
{
}
// Step forward until we pass the ending value.
{
if (cmp >= 0)
{
// We have gone past the ending value.
break;
}
// 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 (deleteOperation != null
{
if (!pluginResult.continueProcessing())
{
throw new DirectoryException(
}
}
if(deleteOperation != null)
{
deleteOperation.checkIfCanceled(false);
}
// Get the next DN.
data = new DatabaseEntry();
}
}
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);
}
// Commit the transaction.
if(isSubtreeDelete)
{
subordinateEntriesDeleted + 1));
}
}
catch (DatabaseException databaseException)
{
throw databaseException;
}
catch (DirectoryException directoryException)
{
throw directoryException;
}
catch (CanceledOperationException coe)
{
throw coe;
}
catch (Exception e)
{
{
}
message, e);
}
}
boolean manageDsaIT,
{
{
// Read the entry ID from dn2id.
{
}
{
}
}
// Remove from dn2id.
{
// Do not expect to ever come through here.
}
// Check that the entry exists in id2entry and read its contents.
{
}
if (!manageDsaIT)
{
}
// Update the referral database.
// Remove from id2entry.
{
}
// Remove from the indexes, in index config order.
// Remove the id2c and id2s records for this entry.
// Iterate up through the superior entries from the target entry.
boolean isParent = true;
{
// Read the ID from dn2id.
{
}
// Remove from id2children.
if (isParent)
{
isParent = false;
}
}
// 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;
}
try
{
}
catch (DatabaseException e)
{
logger.traceException(e);
return false;
}
}
/**
* Fetch an entry by DN, trying the entry cache first, then the database. Retrieves the requested
* entry, trying the entry cache first, then the database.
*
* @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 DatabaseException
* An error occurred during a database operation.
*/
{
if (entryCache != null)
{
{
return entry;
}
}
{
// 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;
}
/**
* 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 DatabaseException If an error occurs in the JE database.
* @throws DirectoryException If a Directory Server error occurs.
* @throws CanceledOperationException if this operation should be cancelled.
*/
{
try
{
// Read dn2id.
{
// The entry does not exist.
}
{
// Check if the entry is a referral entry.
}
// Update the referral database.
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);
}
// Commit the transaction.
// Update the entry cache.
if (entryCache != null)
{
}
}
catch (DatabaseException databaseException)
{
throw databaseException;
}
catch (DirectoryException directoryException)
{
throw directoryException;
}
catch (CanceledOperationException coe)
{
throw coe;
}
catch (Exception e)
{
{
}
message, 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 DatabaseException If an error occurs in the JE database.
*/
{
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)
{
}
{
}
{
/*
* 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())
{
"all entries in the subtree. " +
"Old DN: %s " +
"New DN: %s " +
"Old entry ID: %d " +
"New entry ID: %d " +
"New Superior ID: %d" +
}
}
}
// Move or rename the apex entry.
current);
/*
* 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.
*/
byte special = 0x00;
/*
* Set the ending value to a value of equal length but slightly
* greater than the suffix.
*/
cursorConfig.setReadCommitted(true);
try
{
// Initialize the cursor very close to the starting value.
// Step forward until the key is greater than the starting value.
{
}
// Step forward until we pass the ending value.
{
if (cmp >= 0)
{
// We have gone past the ending value.
break;
}
// 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())
{
"renumbering. " +
"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.
data = new DatabaseEntry();
}
}
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);
}
// Commit the transaction.
}
catch (DatabaseException databaseException)
{
throw databaseException;
}
catch (DirectoryException directoryException)
{
throw directoryException;
}
catch (CanceledOperationException coe)
{
throw coe;
}
catch (Exception e)
{
{
}
message, e);
}
}
/** Represents an renamed entry that was deleted from JE but yet to be added back. */
private static final class MovedEntry
{
private MovedEntry next;
private boolean renumbered;
{
this.renumbered = renumbered;
}
}
boolean isApexEntryMoved,
boolean renumbered,
throws DirectoryException, DatabaseException
{
{
}
{
// Reindex the entry with the new ID.
}
// Add the new ID to id2children and id2subtree of new apex parent entry.
if(isApexEntryMoved)
{
boolean isParent = true;
{
if(isParent)
{
isParent = false;
}
}
}
}
boolean isApexEntryMoved,
throws DirectoryException, DatabaseException
{
// 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.
// Remove the old ID from id2children and id2subtree of
// the old apex parent entry.
{
boolean isParent = true;
{
if(isParent)
{
isParent = false;
}
}
}
{
// All the subordinates will be renumbered so we have to rebuild
// id2c and id2s with the new ID.
// 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,
throws DirectoryException, DatabaseException
{
// 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())
{
{
throw new DirectoryException(
}
}
}
// 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.
if(isApexEntryMoved)
{
// Remove the old ID from id2subtree of old apex superior entries.
{
}
}
{
// All the subordinates will be renumbered so we have to rebuild
// id2c and id2s with the new ID.
// 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 DatabaseException If an error occurs in the JE database.
* @throws DirectoryException If a Directory Server error occurs.
*/
throws DatabaseException, DirectoryException
{
{
}
{
}
}
/**
* 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 DatabaseException If an error occurs in the JE database.
* @throws DirectoryException If a Directory Server error occurs.
*/
throws DatabaseException, DirectoryException
{
{
}
{
}
}
/**
* 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 DatabaseException If an error occurs in the JE database.
* @throws DirectoryException If a Directory Server error occurs.
*/
throws DatabaseException, DirectoryException
{
// 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.
*
* @return The number of entries stored in this entry container.
* @throws DatabaseException If an error occurs in the JE database.
*/
public long getEntryCount() throws DatabaseException
{
{
{
// Add the base entry itself
return ++count;
}
else
{
// The count is not maintained. Fall back to the slow method
return id2entry.getRecordCount();
}
}
else
{
// Base entry doesn't not exist so this entry container
// must not have any entries
return 0;
}
}
/**
* Get the number of values for which the entry limit has been exceeded
* since the entry container was opened.
* @return The number of values for which the entry limit has been exceeded.
*/
public int getEntryLimitExceededCount()
{
int count = 0;
{
}
return count;
}
/**
* Get a list of the databases opened by the entryContainer.
* @param dbList A list of database containers.
*/
{
{
}
{
}
}
/**
* 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;
}
/**
* Begin a leaf transaction using the default configuration.
* Provides assertion debug logging.
* @return A JE transaction handle.
* @throws DatabaseException If an error occurs while attempting to begin
* a new transaction.
*/
public Transaction beginTransaction()
throws DatabaseException
{
if (logger.isTraceEnabled())
{
}
return txn;
}
/**
* Commit a transaction.
* Provides assertion debug logging.
* @param txn The JE transaction handle.
* @throws DatabaseException If an error occurs while attempting to commit
* the transaction.
*/
throws DatabaseException
{
{
if (logger.isTraceEnabled())
{
}
}
}
/**
* Abort a transaction.
* Provides assertion debug logging.
* @param txn The JE transaction handle.
* @throws DatabaseException If an error occurs while attempting to abort the
* transaction.
*/
throws DatabaseException
{
{
if (logger.isTraceEnabled())
{
}
}
}
/**
* Delete this entry container from disk. The entry container should be
* closed before calling this method.
*
* @throws DatabaseException If an error occurs while removing the entry
* container.
*/
void delete() throws DatabaseException
{
{
try
{
{
}
}
catch(DatabaseException de)
{
throw de;
}
}
else
{
{
}
}
}
/**
* Remove a database from disk.
*
* @param database The database container to remove.
* @throws DatabaseException If an error occurs while attempting to delete the
* database.
*/
throws DatabaseException
{
{
// The state database can not be removed individually.
return;
}
{
try
{
{
}
}
catch(DatabaseException de)
{
throw de;
}
}
else
{
{
}
}
}
/**
* Removes a attribute index from disk.
*
* @param attributeIndex The attribute index to remove.
* @throws DatabaseException If an JE database error occurs while attempting
* to delete the index.
*/
throws DatabaseException
{
? beginTransaction() : null;
try
{
{
}
{
}
}
catch(DatabaseException de)
{
{
}
throw de;
}
}
/**
* 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.
*/
public String getDatabasePrefix()
{
return databasePrefix;
}
/**
* Sets a new database prefix for this entry container and rename all
* existing databases in use by this entry container.
*
* @param newDatabasePrefix The new database prefix to use.
* @throws DatabaseException If an error occurs in the JE database.
* @throws JebException If an error occurs in the JE backend.
*/
throws DatabaseException, JebException
{
// close the containers.
{
}
try
{
{
//Rename under transaction
try
{
{
}
{
}
// Update the prefix.
this.databasePrefix = newDatabasePrefix;
}
catch(Exception e)
{
{
}
throw new JebException(message, e);
}
}
else
{
{
}
// Update the prefix.
this.databasePrefix = newDatabasePrefix;
}
}
finally
{
// Open the containers backup.
{
}
}
}
/** {@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
{
{
if (cfg.isSubordinateIndexesEnabled())
{
// Re-enabling subordinate indexes.
}
else
{
// Disabling subordinate indexes. Use a null index and ensure that
// future attempts to use the real indexes will fail.
id2children.close();
id2subtree.close();
}
}
{
{
ccr.setAdminActionRequired(true);
}
{
ccr.setAdminActionRequired(true);
}
}
}
catch (DatabaseException e)
{
}
finally
{
}
return ccr;
}
/**
* Get the environment config of the JE environment used in this entry
* container.
*
* @return The environment config of the JE environment.
* @throws DatabaseException If an error occurs while retrieving the
* configuration object.
*/
{
}
/**
* Clear the contents of this entry container.
*
* @throws DatabaseException If an error occurs while removing the entry
* container.
*/
public void clear() throws DatabaseException
{
{
}
try
{
{
try
{
{
}
}
catch(DatabaseException de)
{
throw de;
}
}
else
{
{
}
}
}
finally
{
{
}
try
{
txn = beginTransaction();
}
{
{
}
}
}
}
{
// This is mainly used during the unit tests, so it's not essential.
try
{
{
}
}
catch (Exception e)
{
}
}
}
}
/**
* Clear the contents for a database from disk.
*
* @param database The database to clear.
* @throws DatabaseException if a JE database error occurs.
*/
throws DatabaseException
{
try
{
{
try
{
}
catch(DatabaseException de)
{
throw de;
}
}
else
{
}
}
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.
*/
{
{
if (entryExists(parentDN))
{
return parentDN;
}
}
return null;
}
/**
* Opens the id2children and id2subtree indexes.
*/
private void openSubordinateIndexes()
{
}
{
{
}
return index;
}
/**
* Creates a new index for an attribute.
*
* @param indexName the name to give to the new index
* @param indexer the indexer to use when inserting data into the index
* @param indexEntryLimit the index entry limit
* @return a new index
*/
{
final int cursorEntryLimit = 100000;
}
/**
* 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
{
// Fetch the base entry.
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;
}
/**
* Transform a database prefix string to one usable by the DB.
* @param databasePrefix the database prefix
* @return a new string when non letter or digit characters
* have been replaced with underscore
*/
{
{
{
}
else
{
}
}
}
/** Get the exclusive lock. */
public void lock() {
}
/** Unlock the exclusive lock. */
public void unlock() {
}
/** {@inheritDoc} */
return databasePrefix;
}
}