opends/build.xml
@@ -1728,14 +1728,14 @@ <echo message=" builds the server without the debug logging facility." /> <echo message=" No debug logging messages will be included on test failures." /> <echo message=""/> <echo message=" -Dorg.opends.test.debug.target=org.opends.server.core:level=verbose,category=data"/> <echo message=" -Dorg.opends.server.debug.target=org.opends.server.core:level=verbose,category=data"/> <echo message=" for example only include debug messages in the core"/> <echo message=" package that are related to data access and at the" /> <echo message=" verbose level or higher. The syntax of this target" /> <echo message=" definition is the same as the org.opends.server.debug.target.x" /> <echo message=" property when starting ${SHORT_NAME}. " /> <echo message=" Default debug target:"/> <echo message=" org.opends.server:level=warning,category=caught|data|database-access|message|protocol,stack" /> <echo message=" org.opends.server:level=warning,category=caught|data|database-access|message|protocol,stack,cause" /> <echo message=""/> <echo message=" -Dtest.diff.srcpath=src/server/org/opends/server/core"/> <echo message=" for example includes only the classes in"/> @@ -1829,12 +1829,12 @@ </not> </condition> <!-- This sets org.opends.test.debug.target if and only if its's not <!-- This sets org.opends.server.debug.target if and only if its's not already set. --> <condition property="org.opends.test.debug.target" value="org.opends.server:level=warning,category=caught|data|database-access|message|protocol,stack"> <condition property="org.opends.server.debug.target" value="org.opends.server:level=warning,category=caught|data|database-access|message|protocol,stack,cause"> <not> <isset property="org.opends.test.debug.target" /> <isset property="org.opends.server.debug.target" /> </not> </condition> @@ -1932,7 +1932,7 @@ <jvmarg value="-Dorg.opends.server.snmp.opendmk=${opendmk.lib.dir}"/> <jvmarg value="-Dorg.opends.test.suppressOutput=${org.opends.test.suppressOutput}" /> <jvmarg value="-Dorg.opends.test.pauseOnFailure=${org.opends.test.pauseOnFailure}" /> <jvmarg value="-Dorg.opends.test.debug.target=${org.opends.test.debug.target}" /> <jvmarg value="-Dorg.opends.server.debug.target=${org.opends.server.debug.target}" /> <jvmarg value="-Dorg.opends.test.copyClassesToTestPackage=${org.opends.test.copyClassesToTestPackage}" /> <jvmarg value="-Dtest.progress=${test.progress}" /> <jvmarg value="-Xms${MEM}" /> opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java
@@ -334,6 +334,68 @@ /** * Update the attribute index for a new entry. * * @param buffer The index buffer to use to store the added keys * @param entryID The entry ID. * @param entry The contents of the new entry. * @return True if all the index keys for the entry are added. False if the * entry ID already exists for some keys. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a Directory Server error occurs. * @throws JebException If an error occurs in the JE backend. */ public boolean addEntry(IndexBuffer buffer, EntryID entryID, Entry entry) throws DatabaseException, DirectoryException, JebException { boolean success = true; if (equalityIndex != null) { if(!equalityIndex.addEntry(buffer, entryID, entry)) { success = false; } } if (presenceIndex != null) { if(!presenceIndex.addEntry(buffer, entryID, entry)) { success = false; } } if (substringIndex != null) { if(!substringIndex.addEntry(buffer, entryID, entry)) { success = false; } } if (orderingIndex != null) { if(!orderingIndex.addEntry(buffer, entryID, entry)) { success = false; } } if (approximateIndex != null) { if(!approximateIndex.addEntry(buffer, entryID, entry)) { success = false; } } return success; } /** * Update the attribute index for a new entry. * * @param txn The database transaction to be used for the insertions. * @param entryID The entry ID. * @param entry The contents of the new entry. @@ -394,6 +456,46 @@ /** * Update the attribute index for a deleted entry. * * @param buffer The index buffer to use to store the deleted keys * @param entryID The entry ID * @param entry The contents of the deleted entry. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a Directory Server error occurs. * @throws JebException If an error occurs in the JE backend. */ public void removeEntry(IndexBuffer buffer, EntryID entryID, Entry entry) throws DatabaseException, DirectoryException, JebException { if (equalityIndex != null) { equalityIndex.removeEntry(buffer, entryID, entry); } if (presenceIndex != null) { presenceIndex.removeEntry(buffer, entryID, entry); } if (substringIndex != null) { substringIndex.removeEntry(buffer, entryID, entry); } if (orderingIndex != null) { orderingIndex.removeEntry(buffer, entryID, entry); } if(approximateIndex != null) { approximateIndex.removeEntry(buffer, entryID, entry); } } /** * Update the attribute index for a deleted entry. * * @param txn The database transaction to be used for the deletions * @param entryID The entry ID * @param entry The contents of the deleted entry. @@ -476,6 +578,51 @@ } /** * Update the index to reflect a sequence of modifications in a Modify * operation. * * @param buffer The index buffer used to buffer up the index changes. * @param entryID The ID of the entry that was modified. * @param oldEntry The entry before the modifications were applied. * @param newEntry The entry after the modifications were applied. * @param mods The sequence of modifications in the Modify operation. * @throws DatabaseException If an error occurs during an operation on a * JE database. */ public void modifyEntry(IndexBuffer buffer, EntryID entryID, Entry oldEntry, Entry newEntry, List<Modification> mods) throws DatabaseException { if (equalityIndex != null) { equalityIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods); } if (presenceIndex != null) { presenceIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods); } if (substringIndex != null) { substringIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods); } if (orderingIndex != null) { orderingIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods); } if (approximateIndex != null) { approximateIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods); } } /** * Makes a byte array representing a substring index key for * one substring of a value. * @@ -1676,4 +1823,41 @@ public Index getPresenceIndex() { return presenceIndex; } /** * Retrieves all the indexes used by this attribute index. * * @return A collection of all indexes in use by this attribute * index. */ public Collection<Index> getAllIndexes() { LinkedHashSet<Index> indexes = new LinkedHashSet<Index>(); if (equalityIndex != null) { indexes.add(equalityIndex); } if (presenceIndex != null) { indexes.add(presenceIndex); } if (substringIndex != null) { indexes.add(substringIndex); } if (orderingIndex != null) { indexes.add(orderingIndex); } if (approximateIndex != null) { indexes.add(approximateIndex); } return indexes; } } opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -30,6 +30,7 @@ import java.io.IOException; import java.io.File; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.io.FileInputStream; import java.io.FilenameFilter; @@ -308,7 +309,7 @@ { EnvironmentConfig envConfig = ConfigurableEnvironment.parseConfigEntry(cfg); envConfig.setLockTimeout(0); rootContainer = initializeRootContainer(envConfig); } @@ -941,9 +942,19 @@ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg); } Lock containerLock = currentContainer.sharedLock; try { currentContainer.sharedLock.lock(); containerLock.lock(); if(currentContainer.getNumSubordinates(currentDN, true) > currentContainer.getSubtreeDeleteBatchSize()) { containerLock.unlock(); containerLock = currentContainer.exclusiveLock; containerLock.lock(); } currentContainer.renameEntry(currentDN, entry, modifyDNOperation); } @@ -966,7 +977,7 @@ } finally { currentContainer.sharedLock.unlock(); containerLock.unlock(); writerEnd(); } } opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java
File was deleted opends/src/server/org/opends/server/backends/jeb/DN2ID.java
@@ -205,10 +205,11 @@ * @param txn A JE database transaction to be used for the database read, or * null if none is required. * @param dn The DN for which the entry ID is desired. * @param lockMode The JE locking mode to be used for the read. * @return The entry ID, or null if the given DN is not in the DN database. * @throws DatabaseException If an error occurs in the JE database. */ public EntryID get(Transaction txn, DN dn) public EntryID get(Transaction txn, DN dn, LockMode lockMode) throws DatabaseException { DatabaseEntry key = DNdata(dn); opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -29,10 +29,7 @@ import com.sleepycat.je.*; import org.opends.server.api.AttributeSyntax; import org.opends.server.api.Backend; import org.opends.server.api.EntryCache; import org.opends.server.api.ClientConnection; import org.opends.server.api.*; import org.opends.server.api.plugin.PluginResult; import org.opends.server.core.AddOperation; import org.opends.server.core.DeleteOperation; @@ -50,6 +47,7 @@ import org.opends.server.types.*; import org.opends.server.util.StaticUtils; import org.opends.server.util.ServerConstants; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import java.util.*; import java.util.concurrent.locks.Lock; @@ -202,8 +200,6 @@ private int subtreeDeleteBatchSize; private int indexEntryLimit; private String databasePrefix; /** * This class is responsible for managing the configuraiton for attribute @@ -314,7 +310,66 @@ public boolean isConfigurationAddAcceptable( LocalDBVLVIndexCfg cfg, List<Message> unacceptableReasons) { // TODO: validate more before returning true? SearchFilter filter; try { filter = SearchFilter.createFilterFromString(cfg.getFilter()); } catch(Exception e) { Message msg = ERR_JEB_CONFIG_VLV_INDEX_BAD_FILTER.get( cfg.getFilter(), cfg.getName(), stackTraceToSingleLineString(e)); unacceptableReasons.add(msg); return false; } String[] sortAttrs = cfg.getSortOrder().split(" "); SortKey[] sortKeys = new SortKey[sortAttrs.length]; OrderingMatchingRule[] orderingRules = new OrderingMatchingRule[sortAttrs.length]; boolean[] ascending = new boolean[sortAttrs.length]; for(int i = 0; i < sortAttrs.length; i++) { try { if(sortAttrs[i].startsWith("-")) { ascending[i] = false; sortAttrs[i] = sortAttrs[i].substring(1); } else { ascending[i] = true; if(sortAttrs[i].startsWith("+")) { sortAttrs[i] = sortAttrs[i].substring(1); } } } catch(Exception e) { Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( String.valueOf(sortKeys[i]), cfg.getName()); unacceptableReasons.add(msg); return false; } AttributeType attrType = DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase()); if(attrType == null) { Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( sortAttrs[i], cfg.getName()); unacceptableReasons.add(msg); return false; } sortKeys[i] = new SortKey(attrType, ascending[i]); orderingRules[i] = attrType.getOrderingMatchingRule(); } return true; } @@ -449,7 +504,6 @@ this.deadlockRetryLimit = config.getDeadlockRetryLimit(); this.subtreeDeleteSizeLimit = config.getSubtreeDeleteSizeLimit(); this.subtreeDeleteBatchSize = config.getSubtreeDeleteBatchSize(); this.indexEntryLimit = config.getIndexEntryLimit(); // Instantiate the attribute indexes. attrIndexMap = new HashMap<AttributeType, AttributeIndex>(); @@ -498,12 +552,12 @@ id2children = new Index(databasePrefix + "_" + ID2CHILDREN_DATABASE_NAME, new ID2CIndexer(), state, indexEntryLimit, 0, true, config.getIndexEntryLimit(), 0, true, env,this); id2children.open(); id2subtree = new Index(databasePrefix + "_" + ID2SUBTREE_DATABASE_NAME, new ID2SIndexer(), state, indexEntryLimit, 0, true, config.getIndexEntryLimit(), 0, true, env, this); id2subtree.open(); @@ -753,7 +807,7 @@ public long getNumSubordinates(DN entryDN, boolean subtree) throws DatabaseException { EntryID entryID = dn2id.get(null, entryDN); EntryID entryID = dn2id.get(null, entryDN, LockMode.DEFAULT); if (entryID != null) { DatabaseEntry key = @@ -1002,7 +1056,7 @@ if (entryIDList.size() > IndexFilter.FILTER_CANDIDATE_THRESHOLD) { // Read the ID from dn2id. EntryID baseID = dn2id.get(null, baseDN); EntryID baseID = dn2id.get(null, baseDN, LockMode.DEFAULT); if (baseID == null) { Message message = @@ -1851,8 +1905,6 @@ public Transaction beginOperationTransaction() throws DatabaseException { Transaction txn = beginTransaction(); // Multiple adds should never encounter a deadlock. txn.setLockTimeout(0); return txn; } @@ -1878,7 +1930,7 @@ throws DatabaseException, DirectoryException, JebException { // Check whether the entry already exists. if (dn2id.get(txn, entry.getDN()) != null) if (dn2id.get(txn, entry.getDN(), LockMode.DEFAULT) != null) { Message message = ERR_JEB_ADD_ENTRY_ALREADY_EXISTS.get(entry.getDN().toString()); @@ -1894,7 +1946,7 @@ dn2uri.targetEntryReferrals(entry.getDN(), null); // Read the parent ID from dn2id. parentID = dn2id.get(txn, parentDN); parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT); if (parentID == null) { Message message = ERR_JEB_ADD_NO_SUCH_OBJECT.get( @@ -1961,7 +2013,7 @@ dn = getParentWithinBase(dn)) { // Read the ID from dn2id. EntryID nodeID = dn2id.get(txn, dn); EntryID nodeID = dn2id.get(txn, dn, LockMode.DEFAULT); if (nodeID == null) { Message msg = @@ -2040,212 +2092,6 @@ } /** * Delete a leaf entry. * The caller must be sure that the entry is indeed a leaf. We cannot * rely on id2children to check for children since this entry may at * one time have had enough children to exceed the index entry limit, * after which the number of children IDs is unknown. * * @param id2cBuffered A buffered children index. * @param id2sBuffered A buffered subtree index. * @param txn The database transaction. * @param leafDN The DN of the leaf entry to be deleted. * @param leafID The ID of the leaf entry. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a Directory Server error occurs. * @throws JebException If an error occurs in the JE backend. */ private void deleteLeaf(BufferedIndex id2cBuffered, BufferedIndex id2sBuffered, Transaction txn, DN leafDN, EntryID leafID) throws DatabaseException, DirectoryException, JebException { // Check that the entry exists in id2entry and read its contents. Entry entry = id2entry.get(txn, leafID); if (entry == null) { Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); throw new JebException(msg); } // Remove from dn2id. if (!dn2id.remove(txn, leafDN)) { // Do not expect to ever come through here. Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); DN matchedDN = getMatchedDN(baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } // Update the referral database. dn2uri.deleteEntry(txn, entry); // Remove from id2entry. if (!id2entry.remove(txn, leafID)) { Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); throw new JebException(msg); } // Remove from the indexes, in index config order. indexRemoveEntry(txn, entry, leafID); // Make sure this entry either has no children in id2children, // or that the index entry limit has been exceeded. byte[] keyID = leafID.getDatabaseEntry().getData(); EntryIDSet children = id2cBuffered.get(keyID); if (!children.isDefined()) { id2cBuffered.remove(keyID); } else if (children.size() != 0) { Message message = ERR_JEB_DELETE_NOT_ALLOWED_ON_NONLEAF.get(leafDN.toString()); throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, message); } // Make sure this entry either has no subordinates in id2subtree, // or that the index entry limit has been exceeded. EntryIDSet subordinates = id2sBuffered.get(keyID); if (!subordinates.isDefined()) { id2sBuffered.remove(keyID); } else if (subordinates.size() != 0) { Message message = ERR_JEB_DELETE_NOT_ALLOWED_ON_NONLEAF.get(leafDN.toString()); throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, message); } // Iterate up through the superior entries. boolean isParent = true; for (DN dn = getParentWithinBase(leafDN); dn != null; dn = getParentWithinBase(dn)) { // Read the ID from dn2id. EntryID nodeID = dn2id.get(txn, dn); if (nodeID == null) { Message msg = ERR_JEB_MISSING_DN2ID_RECORD.get(dn.toNormalizedString()); throw new JebException(msg); } DatabaseEntry nodeIDData = nodeID.getDatabaseEntry(); // Remove from id2children. if (isParent) { id2cBuffered.removeID(nodeIDData.getData(), leafID); isParent = false; } // Remove from id2subtree for this node. id2sBuffered.removeID(nodeIDData.getData(), leafID); } } /** * Delete the target entry of a delete operation, with appropriate handling * of referral entries. The caller must be sure that the entry is indeed a * leaf. * * @param manageDsaIT In the case where the target entry is a referral entry, * this parameter should be true if the target is to be deleted, or false if * the target should generate a referral. * @param id2cBuffered A buffered children index. * @param id2sBuffered A buffered subtree index. * @param txn The database transaction. * @param leafDN The DN of the target entry to be deleted. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a Directory Server error occurs. * @throws JebException If an error occurs in the JE backend. */ private void deleteTarget(boolean manageDsaIT, BufferedIndex id2cBuffered, BufferedIndex id2sBuffered, Transaction txn, DN leafDN) throws DatabaseException, DirectoryException, JebException { // Read the entry ID from dn2id. EntryID leafID = dn2id.get(txn, leafDN); if (leafID == null) { Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); DN matchedDN = getMatchedDN(baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } // Check that the entry exists in id2entry and read its contents. Entry entry = id2entry.get(txn, leafID); if (entry == null) { Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); throw new JebException(msg); } if (!manageDsaIT) { dn2uri.checkTargetForReferral(entry, null); } // Remove from dn2id. if (!dn2id.remove(txn, leafDN)) { // Do not expect to ever come through here. Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); DN matchedDN = getMatchedDN(baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } // Update the referral database. dn2uri.deleteEntry(txn, entry); // Remove from id2entry. if (!id2entry.remove(txn, leafID)) { Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); throw new JebException(msg); } // Remove from the indexes, in index config order. indexRemoveEntry(txn, entry, leafID); // Iterate up through the superior entries. boolean isParent = true; for (DN dn = getParentWithinBase(leafDN); dn != null; dn = getParentWithinBase(dn)) { // Read the ID from dn2id. EntryID nodeID = dn2id.get(txn, dn); if (nodeID == null) { Message msg = ERR_JEB_MISSING_DN2ID_RECORD.get(dn.toNormalizedString()); throw new JebException(msg); } DatabaseEntry nodeIDData = nodeID.getDatabaseEntry(); // Remove from id2children. if (isParent) { id2cBuffered.removeID(nodeIDData.getData(), leafID); isParent = false; } // Remove from id2subtree for this node. id2sBuffered.removeID(nodeIDData.getData(), leafID); } } /** * This inner class implements the Delete Entry operation through * the TransactedOperation interface. */ @@ -2261,12 +2107,6 @@ */ private DeleteOperation deleteOperation; /** * A list of the DNs of all entries deleted by this operation in a batch. * The subtree delete control can cause multiple entries to be deleted. */ private ArrayList<DN> deletedDNList; /** * Indicates whether the subtree delete size limit has been exceeded. @@ -2281,9 +2121,19 @@ /** * Indicates the count of deleted DNs in the Delete Operation. * Indicates the total count of deleted DNs in the Delete Operation. */ private int countDeletedDN; private int totalDeletedDN; /** * Indicates the batch count of deleted DNs in the Delete Operation. */ private int batchDeletedDN; /** * The index buffer used to buffer up the index changes. */ private IndexBuffer indexBuffer = null; /** * Create a new Delete Entry Transaction. @@ -2294,7 +2144,6 @@ { this.entryDN = entryDN; this.deleteOperation = deleteOperation; deletedDNList = new ArrayList<DN>(); } /** @@ -2322,7 +2171,7 @@ public void resetBatchSize() { batchSizeExceeded=false; deletedDNList.clear(); batchDeletedDN = 0; } /** @@ -2331,7 +2180,7 @@ */ public int getDeletedEntryCount() { return countDeletedDN; return totalDeletedDN; } /** @@ -2344,8 +2193,6 @@ public Transaction beginOperationTransaction() throws DatabaseException { Transaction txn = beginTransaction(); // Multiple deletes should never encounter a deadlock. txn.setLockTimeout(0); return txn; } @@ -2364,8 +2211,6 @@ dn2uri.targetEntryReferrals(entryDN, null); // Determine whether this is a subtree delete. int adminSizeLimit = subtreeDeleteSizeLimit; int deleteBatchSize = subtreeDeleteBatchSize; boolean isSubtreeDelete = false; List<Control> controls = deleteOperation.getRequestControls(); if (controls != null) @@ -2403,11 +2248,10 @@ DatabaseEntry data = new DatabaseEntry(); DatabaseEntry key = new DatabaseEntry(begin); CursorConfig cursorConfig = new CursorConfig(); cursorConfig.setReadCommitted(true); BufferedIndex id2cBuffered = new BufferedIndex(id2children, txn); BufferedIndex id2sBuffered = new BufferedIndex(id2subtree, txn); Cursor cursor = dn2id.openCursor(txn, null); Cursor cursor = dn2id.openCursor(txn, cursorConfig); try { OperationStatus status; @@ -2449,30 +2293,38 @@ } // Enforce any subtree delete size limit. if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit) if (subtreeDeleteSizeLimit > 0 && totalDeletedDN >= subtreeDeleteSizeLimit) { adminSizeLimitExceeded = true; break; } // Enforce any subtree delete batch size. if (deleteBatchSize > 0 && deletedDNList.size() >= deleteBatchSize) if (subtreeDeleteBatchSize > 0 && batchDeletedDN >= subtreeDeleteBatchSize) { batchSizeExceeded = true; break; } // This is a subtree delete so crate a index buffer // if it there isn't one. if(indexBuffer == null) { indexBuffer = new IndexBuffer(EntryContainer.this); } /* * Delete this entry which by now must be a leaf because * we have been deleting from the bottom of the tree upwards. */ EntryID entryID = new EntryID(data); DN subordinateDN = DN.decode(new ASN1OctetString(key.getData())); deleteLeaf(id2cBuffered, id2sBuffered, txn, subordinateDN, entryID); deleteEntry(txn, true, entryDN, subordinateDN, entryID); deletedDNList.add(subordinateDN); countDeletedDN++; batchDeletedDN++; totalDeletedDN++; status = cursor.getPrev(key, data, LockMode.DEFAULT); } } @@ -2486,41 +2338,178 @@ if (!adminSizeLimitExceeded && !batchSizeExceeded) { // Enforce any subtree delete size limit. if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit) if (subtreeDeleteSizeLimit > 0 && totalDeletedDN >= subtreeDeleteSizeLimit) { adminSizeLimitExceeded = true; } else if (deleteBatchSize > 0 && deletedDNList.size() >= deleteBatchSize) else if (subtreeDeleteBatchSize > 0 && batchDeletedDN >= subtreeDeleteBatchSize) { batchSizeExceeded = true; } else { boolean manageDsaIT; if (isSubtreeDelete) { // 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. manageDsaIT = true; } else { manageDsaIT = isManageDsaITOperation(deleteOperation); } deleteTarget(manageDsaIT, id2cBuffered, id2sBuffered, txn, entryDN); // 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. deleteEntry(txn, isSubtreeDelete || isManageDsaITOperation(deleteOperation), entryDN, null, null); deletedDNList.add(entryDN); countDeletedDN++; batchDeletedDN++; totalDeletedDN++; } } // Write out any buffered index values. id2cBuffered.flush(); id2sBuffered.flush(); if(indexBuffer != null) { indexBuffer.flush(txn); } } /** * Delete an entry with appropriate handling of referral entries. * The caller must be sure that the entry is indeed a leaf. We cannot * rely on id2children to check for children since this entry may at * one time have had enough children to exceed the index entry limit, * after which the number of children IDs is unknown. * * @param txn The database transaction. * @param manageDsaIT Whether it is an manage DSA IT operation. * @param targetDN The DN of the target entry. * @param leafDN The DN of the leaf entry to be deleted. * @param leafID The ID of the leaf entry. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a Directory Server error occurs. * @throws JebException If an error occurs in the JE backend. */ private void deleteEntry(Transaction txn, boolean manageDsaIT, DN targetDN, DN leafDN, EntryID leafID) throws DatabaseException, DirectoryException, JebException { if(leafID == null || leafDN == null) { // Read the entry ID from dn2id. leafDN = targetDN; leafID = dn2id.get(txn, leafDN, LockMode.RMW); if (leafID == null) { Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); DN matchedDN = getMatchedDN(baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } } // Remove from dn2id. if (!dn2id.remove(txn, leafDN)) { // Do not expect to ever come through here. Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); DN matchedDN = getMatchedDN(baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } // Check that the entry exists in id2entry and read its contents. Entry entry = id2entry.get(txn, leafID, LockMode.RMW); if (entry == null) { Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); throw new JebException(msg); } if (!manageDsaIT) { dn2uri.checkTargetForReferral(entry, null); } // Update the referral database. dn2uri.deleteEntry(txn, entry); // Remove from id2entry. if (!id2entry.remove(txn, leafID)) { Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); throw new JebException(msg); } // Remove from the indexes, in index config order. if(indexBuffer != null) { indexRemoveEntry(indexBuffer, entry, leafID); } else { indexRemoveEntry(txn, entry, leafID); } // Remove the id2c and id2s records for this entry. if(indexBuffer != null) { byte[] leafIDKeyBytes = JebFormat.entryIDToDatabase(leafID.longValue()); id2children.delete(indexBuffer, leafIDKeyBytes); id2subtree.delete(indexBuffer, leafIDKeyBytes); } else { DatabaseEntry leafIDKey = leafID.getDatabaseEntry(); id2children.delete(txn, leafIDKey); id2subtree.delete(txn, leafIDKey); } // Iterate up through the superior entries from the target entry. boolean isParent = true; for (DN parentDN = getParentWithinBase(targetDN); parentDN != null; parentDN = getParentWithinBase(parentDN)) { // Read the ID from dn2id. EntryID parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT); if (parentID == null) { Message msg = ERR_JEB_MISSING_DN2ID_RECORD.get(parentDN.toNormalizedString()); throw new JebException(msg); } if(indexBuffer != null) { byte[] parentIDBytes = JebFormat.entryIDToDatabase(parentID.longValue()); // Remove from id2children. if (isParent) { id2children.removeID(indexBuffer, parentIDBytes, leafID); isParent = false; } id2subtree.removeID(indexBuffer, parentIDBytes, leafID); } else { DatabaseEntry nodeIDData = parentID.getDatabaseEntry(); // Remove from id2children. if(isParent) { id2children.removeID(txn, nodeIDData, leafID); isParent = false; } id2subtree.removeID(txn, nodeIDData, leafID); } } // Remove the entry from the entry cache. EntryCache entryCache = DirectoryServer.getEntryCache(); if (entryCache != null) { entryCache.removeEntry(leafDN); } } /** @@ -2529,15 +2518,7 @@ */ public void postCommitAction() { // Update the entry cache. EntryCache entryCache = DirectoryServer.getEntryCache(); if (entryCache != null) { for (DN dn : deletedDNList) { entryCache.removeEntry(dn); } } } } @@ -2570,7 +2551,7 @@ EntryID id = null; try { id = dn2id.get(null, entryDN); id = dn2id.get(null, entryDN, LockMode.DEFAULT); } catch (DatabaseException e) { @@ -2707,7 +2688,7 @@ JebException { // Read dn2id. entryID = dn2id.get(txn, entryDN); entryID = dn2id.get(txn, entryDN, LockMode.DEFAULT); if (entryID == null) { // The entryDN does not exist. @@ -2719,7 +2700,7 @@ } // Read id2entry. entry = id2entry.get(txn, entryID); entry = id2entry.get(txn, entryID, LockMode.DEFAULT); if (entry == null) { @@ -2811,7 +2792,7 @@ JebException { // Read id2entry. entry = id2entry.get(txn, entryID); entry = id2entry.get(txn, entryID, LockMode.DEFAULT); } /** @@ -2890,8 +2871,6 @@ public Transaction beginOperationTransaction() throws DatabaseException { Transaction txn = beginTransaction(); // Multiple replace operations should never encounter a deadlock. txn.setLockTimeout(0); return txn; } @@ -2908,7 +2887,7 @@ JebException { // Read dn2id. entryID = dn2id.get(txn, entry.getDN()); entryID = dn2id.get(txn, entry.getDN(), LockMode.RMW); if (entryID == null) { // The entry does not exist. @@ -2920,7 +2899,7 @@ } // Read id2entry for the original entry. Entry originalEntry = id2entry.get(txn, entryID); Entry originalEntry = id2entry.get(txn, entryID, LockMode.RMW); if (originalEntry == null) { // The entry does not exist. @@ -3044,16 +3023,10 @@ */ private ModifyDNOperation modifyDNOperation; /** * A buffered children index. * Whether the apex entry moved under another parent. */ private BufferedIndex id2cBuffered; /** * A buffered subtree index. */ private BufferedIndex id2sBuffered; private boolean isApexEntryMoved; /** * Create a new transacted operation for a Modify DN operation. @@ -3069,6 +3042,19 @@ this.newSuperiorDN = getParentWithinBase(entry.getDN()); this.newApexEntry = entry; this.modifyDNOperation = modifyDNOperation; if(oldSuperiorDN != null) { this.isApexEntryMoved = ! oldSuperiorDN.equals(newSuperiorDN); } else if(newSuperiorDN != null) { this.isApexEntryMoved = ! newSuperiorDN.equals(oldSuperiorDN); } else { this.isApexEntryMoved = false; } } /** @@ -3079,19 +3065,13 @@ * @throws DirectoryException If a Directory Server error occurs. * @throws JebException If an error occurs in the JE backend. */ public void invokeOperation(Transaction txn) throws DatabaseException, DirectoryException, JebException public void invokeOperation(Transaction txn) throws DatabaseException, DirectoryException, JebException { DN requestedNewSuperiorDN = null; if(modifyDNOperation != null) { requestedNewSuperiorDN = modifyDNOperation.getNewSuperior(); } IndexBuffer buffer = new IndexBuffer(EntryContainer.this); // Check whether the renamed entry already exists. if (dn2id.get(txn, newApexEntry.getDN()) != null) if (dn2id.get(txn, newApexEntry.getDN(), LockMode.DEFAULT) != null) { Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get( newApexEntry.getDN().toString()); @@ -3099,7 +3079,7 @@ message); } EntryID oldApexID = dn2id.get(txn, oldApexDN); EntryID oldApexID = dn2id.get(txn, oldApexDN, LockMode.DEFAULT); if (oldApexID == null) { // Check for referral entries above the target entry. @@ -3112,7 +3092,7 @@ message, matchedDN, null); } Entry oldApexEntry = id2entry.get(txn, oldApexID); Entry oldApexEntry = id2entry.get(txn, oldApexID, LockMode.DEFAULT); if (oldApexEntry == null) { Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(oldApexID.toString()); @@ -3124,18 +3104,15 @@ dn2uri.checkTargetForReferral(oldApexEntry, null); } id2cBuffered = new BufferedIndex(id2children, txn); id2sBuffered = new BufferedIndex(id2subtree, txn); EntryID newApexID = oldApexID; if (newSuperiorDN != null) if (newSuperiorDN != null && isApexEntryMoved) { /* * 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. */ EntryID newSuperiorID = dn2id.get(txn, newSuperiorDN); EntryID newSuperiorID = dn2id.get(txn, newSuperiorDN, LockMode.DEFAULT); if (newSuperiorID == null) { Message msg = @@ -3153,18 +3130,26 @@ // expensive since every entry has to be deleted from // and added back into the attribute indexes. newApexID = rootContainer.getNextEntryID(); if(debugEnabled()) { TRACER.debugInfo("Move of target entry requires renumbering" + "all entries in the subtree. " + "Old DN: %s " + "New DN: %s " + "Old entry ID: %d " + "New entry ID: %d " + "New Superior ID: %d" + oldApexEntry.getDN(), newApexEntry.getDN(), oldApexID.longValue(), newApexID.longValue(), newSuperiorID.longValue()); } } } // Move or rename the apex entry. if (requestedNewSuperiorDN != null) { moveApexEntry(txn, oldApexID, newApexID, oldApexEntry, newApexEntry); } else { renameApexEntry(txn, oldApexID, oldApexEntry, newApexEntry); } renameApexEntry(txn, buffer, oldApexID, newApexID, oldApexEntry, newApexEntry); /* * We will iterate forwards through a range of the dn2id keys to @@ -3186,20 +3171,23 @@ DatabaseEntry data = new DatabaseEntry(); DatabaseEntry key = new DatabaseEntry(begin); int subordinateEntriesMoved = 0; Cursor cursor = dn2id.openCursor(txn, null); CursorConfig cursorConfig = new CursorConfig(); cursorConfig.setReadCommitted(true); Cursor cursor = dn2id.openCursor(txn, cursorConfig); try { OperationStatus status; // Initialize the cursor very close to the starting value. status = cursor.getSearchKeyRange(key, data, LockMode.RMW); status = cursor.getSearchKeyRange(key, data, LockMode.DEFAULT); // Step forward until the key is greater than the starting value. while (status == OperationStatus.SUCCESS && dn2id.getComparator().compare(key.getData(), begin) <= 0) { status = cursor.getNext(key, data, LockMode.RMW); status = cursor.getNext(key, data, LockMode.DEFAULT); } // Step forward until we pass the ending value. @@ -3215,33 +3203,44 @@ // We have found a subordinate entry. EntryID oldID = new EntryID(data); Entry oldEntry = id2entry.get(txn, oldID); Entry oldEntry = id2entry.get(txn, oldID, LockMode.DEFAULT); // Construct the new DN of the entry. DN newDN = modDN(oldEntry.getDN(), oldApexDN.getNumComponents(), newApexEntry.getDN()); if (requestedNewSuperiorDN != null) // Assign a new entry ID if we are renumbering. EntryID newID = oldID; if (!newApexID.equals(oldApexID)) { // Assign a new entry ID if we are renumbering. EntryID newID = oldID; if (!newApexID.equals(oldApexID)) { newID = rootContainer.getNextEntryID(); } newID = rootContainer.getNextEntryID(); // Move this entry. moveSubordinateEntry(txn, oldID, newID, oldEntry, newDN); if(debugEnabled()) { TRACER.debugInfo("Move of subordinate entry requires " + "renumbering. " + "Old DN: %s " + "New DN: %s " + "Old entry ID: %d " + "New entry ID: %d", oldEntry.getDN(), newDN, oldID.longValue(), newID.longValue()); } } else // Move this entry. renameSubordinateEntry(txn, buffer, oldID, newID, oldEntry, newDN); subordinateEntriesMoved++; if(subordinateEntriesMoved >= subtreeDeleteBatchSize) { // Rename this entry. renameSubordinateEntry(txn, oldID, oldEntry, newDN); buffer.flush(txn); subordinateEntriesMoved = 0; } // Get the next DN. status = cursor.getNext(key, data, LockMode.RMW); status = cursor.getNext(key, data, LockMode.DEFAULT); } } finally @@ -3249,8 +3248,7 @@ cursor.close(); } id2cBuffered.flush(); id2sBuffered.flush(); buffer.flush(txn); } /** @@ -3262,116 +3260,7 @@ */ public Transaction beginOperationTransaction() throws DatabaseException { Transaction txn = beginTransaction(); return txn; } /** * Update the database for the target entry of a ModDN operation * specifying a new superior. * * @param txn The database transaction to be used for the updates. * @param oldID The original ID of the target entry. * @param newID The new ID of the target entry, or the original ID if * the ID has not changed. * @param oldEntry The original contents of the target entry. * @param newEntry The new contents of the target entry. * @throws JebException If an error occurs in the JE backend. * @throws DirectoryException If a Directory Server error occurs. * @throws DatabaseException If an error occurs in the JE database. */ private void moveApexEntry(Transaction txn, EntryID oldID, EntryID newID, Entry oldEntry, Entry newEntry) throws JebException, DirectoryException, DatabaseException { DN oldDN = oldEntry.getDN(); DN newDN = newEntry.getDN(); DN newParentDN = getParentWithinBase(newDN); // Remove the old DN from dn2id. dn2id.remove(txn, oldDN); // Insert the new DN in dn2id. if (!dn2id.insert(txn, newDN, newID)) { Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); } // Update any referral records. dn2uri.replaceEntry(txn, oldEntry, newEntry); // Remove the old ID from id2entry. if (!newID.equals(oldID) || modifyDNOperation == null) { id2entry.remove(txn, oldID); // Remove the old ID from the indexes. indexRemoveEntry(txn, oldEntry, oldID); // Insert the new ID into the indexes. indexInsertEntry(txn, newEntry, newID); } else { // Update indexes only for those attributes that changed. indexModifications(txn, oldEntry, newEntry, oldID, modifyDNOperation.getModifications()); } // Put the new entry in id2entry. id2entry.put(txn, newID, newEntry); // Remove the old parentID:ID from id2children. DN oldParentDN = getParentWithinBase(oldDN); if (oldParentDN != null) { EntryID currentParentID = dn2id.get(txn, oldParentDN); id2cBuffered.removeID(currentParentID.getDatabaseEntry().getData(), oldID); } // Put the new parentID:ID in id2children. if (newParentDN != null) { EntryID parentID = dn2id.get(txn, newParentDN); id2cBuffered.insertID(indexEntryLimit, parentID.getDatabaseEntry().getData(), newID); } // Remove the old nodeID:ID from id2subtree. for (DN dn = getParentWithinBase(oldDN); dn != null; dn = getParentWithinBase(dn)) { EntryID nodeID = dn2id.get(txn, dn); id2sBuffered.removeID(nodeID.getDatabaseEntry().getData(), oldID); } // Put the new nodeID:ID in id2subtree. for (DN dn = newParentDN; dn != null; dn = getParentWithinBase(dn)) { EntryID nodeID = dn2id.get(txn, dn); id2sBuffered.insertID(indexEntryLimit, nodeID.getDatabaseEntry().getData(), newID); } if (!newID.equals(oldID)) { // All the subordinates will be renumbered. id2cBuffered.remove(oldID.getDatabaseEntry().getData()); id2sBuffered.remove(oldID.getDatabaseEntry().getData()); } // Remove the entry from the entry cache. EntryCache entryCache = DirectoryServer.getEntryCache(); if (entryCache != null) { entryCache.removeEntry(oldDN); } return beginTransaction(); } /** @@ -3379,14 +3268,17 @@ * not specifying a new superior. * * @param txn The database transaction to be used for the updates. * @param entryID The ID of the target entry. * @param buffer The index buffer used to buffer up the index changes. * @param oldID The old ID of the target entry. * @param newID The new ID of the target entry. * @param oldEntry The original contents of the target entry. * @param newEntry The new contents of the target entry. * @throws DirectoryException If a Directory Server error occurs. * @throws DatabaseException If an error occurs in the JE database. * @throws JebException if an error occurs in the JE database. */ private void renameApexEntry(Transaction txn, EntryID entryID, private void renameApexEntry(Transaction txn, IndexBuffer buffer, EntryID oldID, EntryID newID, Entry oldEntry, Entry newEntry) throws DirectoryException, DatabaseException, JebException { @@ -3396,33 +3288,83 @@ // Remove the old DN from dn2id. dn2id.remove(txn, oldDN); // Insert the new DN in dn2id. if (!dn2id.insert(txn, newDN, entryID)) // Put the new DN in dn2id. if (!dn2id.insert(txn, newDN, newID)) { Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); message); } // Remove old ID from id2entry and put the new entry // (old entry with new DN) in id2entry. if (!newID.equals(oldID)) { id2entry.remove(txn, oldID); } id2entry.put(txn, newID, newEntry); // Update any referral records. dn2uri.replaceEntry(txn, oldEntry, newEntry); // Replace the entry in id2entry. id2entry.put(txn, entryID, newEntry); if(modifyDNOperation == null) // Remove the old ID from id2children and id2subtree of // the old apex parent entry. if(oldSuperiorDN != null && isApexEntryMoved) { // Remove the old ID from the indexes. indexRemoveEntry(txn, oldEntry, entryID); EntryID parentID; byte[] parentIDKeyBytes; boolean isParent = true; for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn)) { parentID = dn2id.get(txn, dn, LockMode.DEFAULT); parentIDKeyBytes = JebFormat.entryIDToDatabase(parentID.longValue()); if(isParent) { id2children.removeID(buffer, parentIDKeyBytes, oldID); isParent = false; } id2subtree.removeID(buffer, parentIDKeyBytes, oldID); } } // Insert the new ID into the indexes. indexInsertEntry(txn, newEntry, entryID); if (!newID.equals(oldID) || modifyDNOperation == null) { // All the subordinates will be renumbered so we have to rebuild // id2c and id2s with the new ID. byte[] oldIDKeyBytes = JebFormat.entryIDToDatabase(oldID.longValue()); id2children.delete(buffer, oldIDKeyBytes); id2subtree.delete(buffer, oldIDKeyBytes); // Reindex the entry with the new ID. indexRemoveEntry(buffer, oldEntry, oldID); indexInsertEntry(buffer, newEntry, newID); } else { // Update indexes only for those attributes that changed. indexModifications(txn, oldEntry, newEntry, entryID, modifyDNOperation.getModifications()); // Update the indexes if needed. indexModifications(buffer, oldEntry, newEntry, oldID, modifyDNOperation.getModifications()); } // Add the new ID to id2children and id2subtree of new apex parent entry. if(newSuperiorDN != null && isApexEntryMoved) { EntryID parentID; byte[] parentIDKeyBytes; boolean isParent = true; for (DN dn = newSuperiorDN; dn != null; dn = getParentWithinBase(dn)) { parentID = dn2id.get(txn, dn, LockMode.DEFAULT); parentIDKeyBytes = JebFormat.entryIDToDatabase(parentID.longValue()); if(isParent) { id2children.insertID(buffer, parentIDKeyBytes, newID); isParent = false; } id2subtree.insertID(buffer, parentIDKeyBytes, newID); } } // Remove the entry from the entry cache. @@ -3438,6 +3380,7 @@ * of a Modify DN operation specifying a new superior. * * @param txn The database transaction to be used for the updates. * @param buffer The index buffer used to buffer up the index changes. * @param oldID The original ID of the subordinate entry. * @param newID The new ID of the subordinate entry, or the original ID if * the ID has not changed. @@ -3447,13 +3390,57 @@ * @throws DirectoryException If a Directory Server error occurs. * @throws DatabaseException If an error occurs in the JE database. */ private void moveSubordinateEntry(Transaction txn, EntryID oldID, EntryID newID, Entry oldEntry, DN newDN) private void renameSubordinateEntry(Transaction txn, IndexBuffer buffer, EntryID oldID, EntryID newID, Entry oldEntry, DN newDN) throws JebException, DirectoryException, DatabaseException { DN oldDN = oldEntry.getDN(); DN newParentDN = getParentWithinBase(newDN); Entry newEntry = oldEntry.duplicate(false); newEntry.setDN(newDN); 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 (! modifyDNOperation.isSynchronizationOperation()) { PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager(); PluginResult.SubordinateModifyDN pluginResult = pluginManager.invokeSubordinateModifyDNPlugins( modifyDNOperation, oldEntry, newEntry, modifications); if (!pluginResult.continueProcessing()) { Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_PLUGIN.get( oldDN.toString(), newDN.toString()); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message); } if (! modifications.isEmpty()) { MessageBuilder invalidReason = new MessageBuilder(); if (! newEntry.conformsToSchema(null, false, false, false, invalidReason)) { Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_SCHEMA_ERROR.get( oldDN.toString(), newDN.toString(), invalidReason.toString()); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message); } } } // Remove the old DN from dn2id. dn2id.remove(txn, oldDN); @@ -3463,212 +3450,84 @@ { Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); message); } // Delete any existing referral records for the old DN. dn2uri.deleteEntry(txn, oldEntry); // Remove old ID from id2entry. // Remove old ID from id2entry and put the new entry // (old entry with new DN) in id2entry. if (!newID.equals(oldID)) { id2entry.remove(txn, oldID); // Update the attribute indexes. for (AttributeIndex index : attrIndexMap.values()) { index.removeEntry(txn, oldID, oldEntry); index.addEntry(txn, newID, oldEntry); } } // 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. Entry newEntry = oldEntry.duplicate(false); newEntry.setDN(newDN); if (! modifyDNOperation.isSynchronizationOperation()) { PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager(); List<Modification> modifications = Collections.unmodifiableList(new ArrayList<Modification>(0)); PluginResult.SubordinateModifyDN pluginResult = pluginManager.invokeSubordinateModifyDNPlugins( modifyDNOperation, oldEntry, newEntry, modifications); if (!pluginResult.continueProcessing()) { Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_PLUGIN.get( oldDN.toString(), newDN.toString()); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message); } if (! modifications.isEmpty()) { indexModifications(txn, oldEntry, newEntry, newID, modifications); MessageBuilder invalidReason = new MessageBuilder(); if (! newEntry.conformsToSchema(null, false, false, false, invalidReason)) { Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_SCHEMA_ERROR.get( oldDN.toString(), newDN.toString(), invalidReason.toString()); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message); } } } // Add any referral records for the new DN. dn2uri.addEntry(txn, newEntry); // Put the new entry (old entry with new DN) in id2entry. id2entry.put(txn, newID, newEntry); // Update any referral records. dn2uri.replaceEntry(txn, oldEntry, newEntry); if(isApexEntryMoved) { // Remove the old ID from id2subtree of old apex superior entries. for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn)) { EntryID parentID = dn2id.get(txn, dn, LockMode.DEFAULT); byte[] parentIDKeyBytes = JebFormat.entryIDToDatabase(parentID.longValue()); id2subtree.removeID(buffer, parentIDKeyBytes, oldID); } } if (!newID.equals(oldID)) { // All the subordinates will be renumbered. id2cBuffered.remove(oldID.getDatabaseEntry().getData()); id2sBuffered.remove(oldID.getDatabaseEntry().getData()); // All the subordinates will be renumbered so we have to rebuild // id2c and id2s with the new ID. byte[] oldIDKeyBytes = JebFormat.entryIDToDatabase(oldID.longValue()); id2children.delete(buffer, oldIDKeyBytes); id2subtree.delete(buffer, oldIDKeyBytes); // Put the new parentID:ID in id2children. if (newParentDN != null) // Add new ID to the id2c and id2s of our new parent and // new ID to id2s up the tree. EntryID newParentID; byte[] parentIDKeyBytes; boolean isParent = true; for (DN superiorDN = newDN; superiorDN != null; superiorDN = getParentWithinBase(superiorDN)) { EntryID parentID = dn2id.get(txn, newParentDN); id2cBuffered.insertID(indexEntryLimit, parentID.getDatabaseEntry().getData(), newID); } } // Remove the old nodeID:ID from id2subtree for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn)) { EntryID nodeID = dn2id.get(txn, dn); id2sBuffered.removeID(nodeID.getDatabaseEntry().getData(), oldID); } // Put the new nodeID:ID in id2subtree. for (DN dn = newParentDN; dn != null; dn = getParentWithinBase(dn)) { if (!newID.equals(oldID) || dn.isAncestorOf(newSuperiorDN)) { EntryID nodeID = dn2id.get(txn, dn); id2sBuffered.insertID(indexEntryLimit, nodeID.getDatabaseEntry().getData(), newID); } } // Remove the entry from the entry cache. EntryCache entryCache = DirectoryServer.getEntryCache(); if (entryCache != null) { entryCache.removeEntry(oldDN); } } /** * Update the database for a subordinate entry of the target entry * of a Modify DN operation not specifying a new superior. * * @param txn The database transaction to be used for the updates. * @param entryID The ID of the subordinate entry. * @param oldEntry The original contents of the subordinate entry. * @param newDN The new DN of the subordinate entry. * @throws DirectoryException If a Directory Server error occurs. * @throws DatabaseException If an error occurs in the JE database. */ private void renameSubordinateEntry(Transaction txn, EntryID entryID, Entry oldEntry, DN newDN) throws DirectoryException, JebException, DatabaseException { DN oldDN = oldEntry.getDN(); // Remove the old DN from dn2id. dn2id.remove(txn, oldDN); // Insert the new DN in dn2id. if (!dn2id.insert(txn, newDN, entryID)) { Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); } // Delete any existing referral records for the old DN. dn2uri.deleteEntry(txn, oldEntry); // 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. Entry newEntry = oldEntry.duplicate(false); newEntry.setDN(newDN); if (! modifyDNOperation.isSynchronizationOperation()) { PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager(); List<Modification> modifications = Collections.unmodifiableList(new ArrayList<Modification>(0)); PluginResult.SubordinateModifyDN pluginResult = pluginManager.invokeSubordinateModifyDNPlugins( modifyDNOperation, oldEntry, newEntry, modifications); if (!pluginResult.continueProcessing()) { Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_PLUGIN.get( oldDN.toString(), newDN.toString()); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message); } if (! modifications.isEmpty()) { indexModifications(txn, oldEntry, newEntry, entryID, modifications); MessageBuilder invalidReason = new MessageBuilder(); if (! newEntry.conformsToSchema(null, false, false, false, invalidReason)) newParentID = dn2id.get(txn, superiorDN, LockMode.DEFAULT); parentIDKeyBytes = JebFormat.entryIDToDatabase(newParentID.longValue()); if(isParent) { Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_SCHEMA_ERROR.get( oldDN.toString(), newDN.toString(), invalidReason.toString()); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message); id2children.insertID(buffer, parentIDKeyBytes, newID); isParent = false; } id2subtree.insertID(buffer, parentIDKeyBytes, newID); } // Reindex the entry with the new ID. indexRemoveEntry(buffer, oldEntry, oldID); indexInsertEntry(buffer, newEntry, newID); } else { // Update the indexes if needed. if(! modifications.isEmpty()) { indexModifications(buffer, oldEntry, newEntry, oldID, modifications); } if(isApexEntryMoved) { // Add the new ID to the id2s of new apex superior entries. for(DN dn = newSuperiorDN; dn != null; dn = getParentWithinBase(dn)) { EntryID parentID = dn2id.get(txn, dn, LockMode.DEFAULT); byte[] parentIDKeyBytes = JebFormat.entryIDToDatabase(parentID.longValue()); id2subtree.insertID(buffer, parentIDKeyBytes, newID); } } } // Add any referral records for the new DN. dn2uri.addEntry(txn, newEntry); // Replace the entry in id2entry. id2entry.put(txn, entryID, newEntry); // Remove the entry from the entry cache. EntryCache entryCache = DirectoryServer.getEntryCache(); if (entryCache != null) @@ -3790,6 +3649,31 @@ } /** * 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 JebException If an error occurs in the JE backend. */ private void indexInsertEntry(IndexBuffer buffer, Entry entry, EntryID entryID) throws DatabaseException, DirectoryException, JebException { for (AttributeIndex index : attrIndexMap.values()) { index.addEntry(buffer, entryID, entry); } for (VLVIndex vlvIndex : vlvIndexMap.values()) { vlvIndex.addEntry(buffer, entryID, entry); } } /** * Remove an entry from the attribute indexes. * * @param txn The database transaction to be used for the updates. @@ -3814,6 +3698,31 @@ } /** * 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 JebException If an error occurs in the JE backend. */ private void indexRemoveEntry(IndexBuffer buffer, Entry entry, EntryID entryID) throws DatabaseException, DirectoryException, JebException { for (AttributeIndex index : attrIndexMap.values()) { index.removeEntry(buffer, entryID, entry); } for (VLVIndex vlvIndex : vlvIndexMap.values()) { vlvIndex.removeEntry(buffer, entryID, entry); } } /** * Update the attribute indexes to reflect the changes to the * attributes of an entry resulting from a sequence of modifications. * @@ -3871,6 +3780,63 @@ } /** * 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 JebException If an error occurs in the JE backend. */ private void indexModifications(IndexBuffer buffer, Entry oldEntry, Entry newEntry, EntryID entryID, List<Modification> mods) throws DatabaseException, DirectoryException, JebException { // Process in index configuration order. for (AttributeIndex index : attrIndexMap.values()) { // Check whether any modifications apply to this indexed attribute. boolean attributeModified = false; AttributeType indexAttributeType = index.getAttributeType(); Iterable<AttributeType> subTypes = DirectoryServer.getSchema().getSubTypes(indexAttributeType); for (Modification mod : mods) { Attribute modAttr = mod.getAttribute(); AttributeType modAttrType = modAttr.getAttributeType(); if (modAttrType.equals(indexAttributeType)) { attributeModified = true; break; } for(AttributeType subType : subTypes) { if(modAttrType.equals(subType)) { attributeModified = true; break; } } } if (attributeModified) { index.modifyEntry(buffer, entryID, oldEntry, newEntry, mods); } } for(VLVIndex vlvIndex : vlvIndexMap.values()) { vlvIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods); } } /** * Get a count of the number of entries stored in this entry entryContainer. * * @return The number of entries stored in this entry entryContainer. @@ -3878,7 +3844,7 @@ */ public long getEntryCount() throws DatabaseException { EntryID entryID = dn2id.get(null, baseDN); EntryID entryID = dn2id.get(null, baseDN, LockMode.DEFAULT); if (entryID != null) { DatabaseEntry key = @@ -4386,7 +4352,6 @@ this.deadlockRetryLimit = config.getDeadlockRetryLimit(); this.subtreeDeleteSizeLimit = config.getSubtreeDeleteSizeLimit(); this.subtreeDeleteBatchSize = config.getSubtreeDeleteBatchSize(); this.indexEntryLimit = config.getIndexEntryLimit(); return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired, messages); } @@ -4671,7 +4636,6 @@ /** * Get the exclusive lock. * */ public void lock() { exclusiveLock.lock(); @@ -4684,4 +4648,14 @@ exclusiveLock.unlock(); } /** * Get the subtree delete batch size. * * @return The subtree delete batch size. */ public int getSubtreeDeleteBatchSize() { return subtreeDeleteBatchSize; } } opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java
@@ -498,33 +498,22 @@ /** * Add all the IDs from a given set that are not already present. * * @param that The set of IDs to be added. * @param that The set of IDs to be added. It MUST be defined */ public void addAll(EntryIDSet that) { if (!this.isDefined()) if(!that.isDefined()) { // Can't simply add the undefined size of this set to that set since // we don't know if there are any duplicates. In this case, we can't // maintain the undefined size anymore. if(undefinedSize != Long.MAX_VALUE && that.size() > 0) { undefinedSize = Long.MAX_VALUE; } return; } if (!that.isDefined()) if (!this.isDefined()) { if(that.size() == 0) // Assume there are no overlap between IDs in that set with this set if(undefinedSize != Long.MAX_VALUE && that.size() > 0) { undefinedSize = values.length; undefinedSize += that.size(); } else { undefinedSize = Long.MAX_VALUE; } values = null; return; } @@ -616,10 +605,15 @@ /** * Delete all IDs in this set that are in a given set. * * @param that The set of IDs to be deleted. * @param that The set of IDs to be deleted. It MUST be defined. */ public void deleteAll(EntryIDSet that) { if(!that.isDefined()) { return; } if (!this.isDefined()) { // Can't simply subtract the undefined size of this set to that set since @@ -632,20 +626,6 @@ return; } if (!that.isDefined()) { if(that.size() == 0) { undefinedSize = values.length; } else { undefinedSize = Long.MAX_VALUE; } values = null; return; } long[] a = this.values; long[] b = that.values; opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java
@@ -50,7 +50,7 @@ import static org.opends.messages.JebMessages.*; import static org.opends.server.util.StaticUtils.*; import com.sleepycat.je.LockMode; /** @@ -98,7 +98,7 @@ { try { Entry e = id2Entry.get(null, id); Entry e = id2Entry.get(null, id, LockMode.DEFAULT); if ((! e.matchesBaseAndScope(baseDN, scope)) || (! filter.matchesEntry(e))) opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
@@ -211,11 +211,12 @@ * * @param txn The database transaction or null if none. * @param id The desired entry ID which forms the key. * @param lockMode The JE locking mode to be used for the read. * @return The requested entry, or null if there is no such record. * @throws JebException If an error occurs in the JE backend. * @throws DatabaseException If an error occurs in the JE database. */ public Entry get(Transaction txn, EntryID id) public Entry get(Transaction txn, EntryID id, LockMode lockMode) throws JebException, DatabaseException { DatabaseEntry key = id.getDatabaseEntry(); opends/src/server/org/opends/server/backends/jeb/Index.java
@@ -185,6 +185,55 @@ } /** * Add an add entry ID operation into a index buffer. * * @param buffer The index buffer to insert the ID into. * @param keyBytes The index key bytes. * @param entryID The entry ID. * @return True if the entry ID is inserted or ignored because the entry limit * count is exceeded. False if it already exists in the entry ID set * for the given key. */ public boolean insertID(IndexBuffer buffer, byte[] keyBytes, EntryID entryID) { TreeMap<byte[], IndexBuffer.BufferedIndexValues> bufferedOperations = buffer.getBufferedIndex(this); IndexBuffer.BufferedIndexValues values = null; if(bufferedOperations == null) { bufferedOperations = new TreeMap<byte[], IndexBuffer.BufferedIndexValues>(comparator); buffer.putBufferedIndex(this, bufferedOperations); } else { values = bufferedOperations.get(keyBytes); } if(values == null) { values = new IndexBuffer.BufferedIndexValues(); bufferedOperations.put(keyBytes, values); } if(values.deletedIDs != null && values.deletedIDs.contains(entryID)) { values.deletedIDs.remove(entryID); return true; } if(values.addedIDs == null) { values.addedIDs = new EntryIDSet(keyBytes, null); } values.addedIDs.add(entryID); return true; } /** * Insert an entry ID into the set of IDs indexed by a given key. * * @param txn A database transaction, or null if none is required. @@ -378,6 +427,276 @@ } /** * Update the set of entry IDs for a given key. * * @param txn A database transaction, or null if none is required. * @param key The database key. * @param deletedIDs The IDs to remove for the key. * @param addedIDs the IDs to add for the key. * @throws DatabaseException If a database error occurs. */ void updateKey(Transaction txn, DatabaseEntry key, EntryIDSet deletedIDs, EntryIDSet addedIDs) throws DatabaseException { OperationStatus status; DatabaseEntry data = new DatabaseEntry(); // Handle cases where nothing is changed early to avoid // DB access. if(deletedIDs != null && deletedIDs.size() == 0 && (addedIDs == null || addedIDs.size() == 0)) { return; } if(addedIDs != null && addedIDs.size() == 0 && (deletedIDs == null || deletedIDs.size() == 0)) { return; } if(deletedIDs == null && addedIDs == null) { status = delete(txn, key); if(status != OperationStatus.SUCCESS) { if(debugEnabled()) { StringBuilder builder = new StringBuilder(); StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4); TRACER.debugError("The expected key does not exist in the " + "index %s.\nKey:%s", name, builder.toString()); } } return; } if(maintainCount) { for(int i = 0; i < phantomWriteRetires; i++) { if(updateKeyWithRMW(txn, key, data, deletedIDs, addedIDs) == OperationStatus.SUCCESS) { return; } } } else { status = read(txn, key, data, LockMode.READ_COMMITTED); if(status == OperationStatus.SUCCESS) { EntryIDSet entryIDList = new EntryIDSet(key.getData(), data.getData()); if (entryIDList.isDefined()) { for(int i = 0; i < phantomWriteRetires; i++) { if(updateKeyWithRMW(txn, key, data, deletedIDs, addedIDs) == OperationStatus.SUCCESS) { return; } } } } else { if(rebuildRunning || trusted) { if(deletedIDs != null) { if(debugEnabled()) { StringBuilder builder = new StringBuilder(); StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4); TRACER.debugError("The expected key does not exist in the " + "index %s.\nKey:%s", name, builder.toString()); } } data.setData(addedIDs.toDatabase()); status = insert(txn, key, data); if(status == OperationStatus.KEYEXIST) { for(int i = 1; i < phantomWriteRetires; i++) { if(updateKeyWithRMW(txn, key, data, deletedIDs, addedIDs) == OperationStatus.SUCCESS) { return; } } } } } } } private OperationStatus updateKeyWithRMW(Transaction txn, DatabaseEntry key, DatabaseEntry data, EntryIDSet deletedIDs, EntryIDSet addedIDs) throws DatabaseException { OperationStatus status; status = read(txn, key, data, LockMode.RMW); if(status == OperationStatus.SUCCESS) { EntryIDSet entryIDList = new EntryIDSet(key.getData(), data.getData()); if(addedIDs != null) { if(entryIDList.isDefined() && indexEntryLimit > 0) { long idCountDelta = addedIDs.size(); if(deletedIDs != null) { idCountDelta -= deletedIDs.size(); } if(idCountDelta + entryIDList.size() >= indexEntryLimit) { if(maintainCount) { entryIDList = new EntryIDSet(entryIDList.size() + idCountDelta); } else { entryIDList = new EntryIDSet(); } entryLimitExceededCount++; if(debugEnabled()) { StringBuilder builder = new StringBuilder(); StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4); TRACER.debugInfo("Index entry exceeded in index %s. " + "Limit: %d. ID list size: %d.\nKey:", name, indexEntryLimit, idCountDelta + addedIDs.size(), builder); } } else { entryIDList.addAll(addedIDs); if(deletedIDs != null) { entryIDList.deleteAll(deletedIDs); } } } else { entryIDList.addAll(addedIDs); if(deletedIDs != null) { entryIDList.deleteAll(deletedIDs); } } } else if(deletedIDs != null) { entryIDList.deleteAll(deletedIDs); } byte[] after = entryIDList.toDatabase(); if (after == null) { // No more IDs, so remove the key. If index is not // trusted then this will cause all subsequent reads // for this key to return undefined set. return delete(txn, key); } else { data.setData(after); return put(txn, key, data); } } else { if(rebuildRunning || trusted) { if(deletedIDs != null) { if(debugEnabled()) { StringBuilder builder = new StringBuilder(); StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4); TRACER.debugError("The expected key does not exist in the " + "index %s.\nKey:%s", name, builder.toString()); } } data.setData(addedIDs.toDatabase()); return insert(txn, key, data); } else { return OperationStatus.SUCCESS; } } } /** * Add an remove entry ID operation into a index buffer. * * @param buffer The index buffer to insert the ID into. * @param keyBytes The index key bytes. * @param entryID The entry ID. * @return True if the entry ID is inserted or ignored because the entry limit * count is exceeded. False if it already exists in the entry ID set * for the given key. */ public boolean removeID(IndexBuffer buffer, byte[] keyBytes, EntryID entryID) { TreeMap<byte[], IndexBuffer.BufferedIndexValues> bufferedOperations = buffer.getBufferedIndex(this); IndexBuffer.BufferedIndexValues values = null; if(bufferedOperations == null) { bufferedOperations = new TreeMap<byte[], IndexBuffer.BufferedIndexValues>(comparator); buffer.putBufferedIndex(this, bufferedOperations); } else { values = bufferedOperations.get(keyBytes); } if(values == null) { values = new IndexBuffer.BufferedIndexValues(); bufferedOperations.put(keyBytes, values); } if(values.addedIDs != null && values.addedIDs.contains(entryID)) { values.addedIDs.remove(entryID); return true; } if(values.deletedIDs == null) { values.deletedIDs = new EntryIDSet(keyBytes, null); } values.deletedIDs.add(entryID); return true; } /** * Remove an entry ID from the set of IDs indexed by a given key. * * @param txn A database transaction, or null if none is required. @@ -516,6 +835,35 @@ } /** * Buffered delete of a key from the JE database. * @param buffer The index buffer to use to store the deleted keys * @param keyBytes The index key bytes. */ public void delete(IndexBuffer buffer, byte[] keyBytes) { TreeMap<byte[], IndexBuffer.BufferedIndexValues> bufferedOperations = buffer.getBufferedIndex(this); IndexBuffer.BufferedIndexValues values = null; if(bufferedOperations == null) { bufferedOperations = new TreeMap<byte[], IndexBuffer.BufferedIndexValues>(comparator); buffer.putBufferedIndex(this, bufferedOperations); } else { values = bufferedOperations.get(keyBytes); } if(values == null) { values = new IndexBuffer.BufferedIndexValues(); bufferedOperations.put(keyBytes, values); } } /** * Check if an entry ID is in the set of IDs indexed by a given key. * * @param txn A database transaction, or null if none is required. @@ -790,6 +1138,36 @@ } /** * Update the index buffer for a deleted entry. * * @param buffer The index buffer to use to store the deleted keys * @param entryID The entry ID. * @param entry The entry to be indexed. * @return True if all the indexType keys for the entry are added. False if * the entry ID already exists for some keys. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a Directory Server error occurs. */ public boolean addEntry(IndexBuffer buffer, EntryID entryID, Entry entry) throws DatabaseException, DirectoryException { HashSet<byte[]> addKeys = new HashSet<byte[]>(); boolean success = true; indexer.indexEntry(null, entry, addKeys); for (byte[] keyBytes : addKeys) { if(!insertID(buffer, keyBytes, entryID)) { success = false; } } return success; } /** * Update the index for a new entry. * * @param txn A database transaction, or null if none is required. @@ -821,6 +1199,27 @@ return success; } /** * Update the index buffer for a deleted entry. * * @param buffer The index buffer to use to store the deleted keys * @param entryID The entry ID * @param entry The contents of the deleted entry. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a Directory Server error occurs. */ public void removeEntry(IndexBuffer buffer, EntryID entryID, Entry entry) throws DatabaseException, DirectoryException { HashSet<byte[]> delKeys = new HashSet<byte[]>(); indexer.indexEntry(null, entry, delKeys); for (byte[] keyBytes : delKeys) { removeID(buffer, keyBytes, entryID); } } /** * Update the index for a deleted entry. @@ -885,6 +1284,40 @@ } /** * Update the index to reflect a sequence of modifications in a Modify * operation. * * @param buffer The index buffer to use to store the deleted keys * @param entryID The ID of the entry that was modified. * @param oldEntry The entry before the modifications were applied. * @param newEntry The entry after the modifications were applied. * @param mods The sequence of modifications in the Modify operation. * @throws DatabaseException If an error occurs in the JE database. */ public void modifyEntry(IndexBuffer buffer, EntryID entryID, Entry oldEntry, Entry newEntry, List<Modification> mods) throws DatabaseException { TreeSet<byte[]> addKeys = new TreeSet<byte[]>(indexer.getComparator()); TreeSet<byte[]> delKeys = new TreeSet<byte[]>(indexer.getComparator()); indexer.modifyEntry(null, oldEntry, newEntry, mods, addKeys, delKeys); for (byte[] keyBytes : delKeys) { removeID(buffer, keyBytes, entryID); } for (byte[] keyBytes : addKeys) { insertID(buffer, keyBytes, entryID); } } /** * Set the index entry limit. * * @param indexEntryLimit The index entry limit to set. opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java
@@ -569,7 +569,7 @@ dn2uri.targetEntryReferrals(entry.getDN(), null); // Read the parent ID from dn2id. EntryID parentID = dn2id.get(txn, parentDN); EntryID parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT); if (parentID != null) { // Insert into id2children for parent ID. @@ -684,7 +684,7 @@ dn2uri.targetEntryReferrals(entry.getDN(), null); // Read the parent ID from dn2id. EntryID parentID = dn2id.get(txn, parentDN); EntryID parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT); if (parentID != null) { // Insert into id2subtree for parent ID. @@ -700,7 +700,7 @@ dn = ec.getParentWithinBase(dn)) { // Read the ID from dn2id. EntryID nodeID = dn2id.get(null, dn); EntryID nodeID = dn2id.get(null, dn, LockMode.DEFAULT); if (nodeID != null) { // Insert into id2subtree for this node. @@ -921,7 +921,7 @@ skippedEntries++; Message message = ERR_JEB_REBUILD_INSERT_ENTRY_FAILED.get( index.getName(), stackTraceToSingleLineString(e)); vlvIndex.getName(), stackTraceToSingleLineString(e)); logError(message); if (debugEnabled()) opends/src/server/org/opends/server/backends/jeb/SortValuesSet.java
@@ -28,8 +28,9 @@ import org.opends.server.types.*; import org.opends.server.protocols.asn1.ASN1OctetString; import org.opends.server.protocols.asn1.ASN1Element; import java.util.HashMap; import java.util.LinkedList; import com.sleepycat.je.DatabaseException; @@ -39,38 +40,28 @@ */ public class SortValuesSet { private static final int ENCODED_VALUE_SIZE = 16; private static final int ENCODED_VALUE_LENGTH_SIZE = encodedLengthSize(ENCODED_VALUE_SIZE); private static final int ENCODED_ATTRIBUTE_VALUE_SIZE = ENCODED_VALUE_LENGTH_SIZE + ENCODED_VALUE_SIZE; private ID2Entry id2entry; private long[] entryIDs; private int[] valuesBytesOffsets; private byte[] valuesBytes; private byte[] keyBytes; private HashMap<EntryID, AttributeValue[]> cachedAttributeValues; private VLVIndex vlvIndex; /** * Construct an empty sort values set with the given information. * * @param vlvIndex The VLV index using this set. * @param id2entry The ID2Entry database. */ public SortValuesSet(VLVIndex vlvIndex, ID2Entry id2entry) public SortValuesSet(VLVIndex vlvIndex) { this.keyBytes = new byte[0]; this.entryIDs = null; this.valuesBytes = null; this.id2entry = id2entry; this.valuesBytesOffsets = null; this.vlvIndex = vlvIndex; this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>(); } /** @@ -79,16 +70,11 @@ * @param keyBytes The database key used to locate this set. * @param dataBytes The bytes to decode and construct this set. * @param vlvIndex The VLV index using this set. * @param id2entry The ID2Entry database. */ public SortValuesSet(byte[] keyBytes, byte[] dataBytes, VLVIndex vlvIndex, ID2Entry id2entry) public SortValuesSet(byte[] keyBytes, byte[] dataBytes, VLVIndex vlvIndex) { this.keyBytes = keyBytes; this.id2entry = id2entry; this.vlvIndex = vlvIndex; this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>(); if(dataBytes == null) { entryIDs = new long[0]; @@ -96,22 +82,16 @@ } entryIDs = getEncodedIDs(dataBytes, 0); valuesBytes = new byte[entryIDs.length * ENCODED_ATTRIBUTE_VALUE_SIZE * vlvIndex.sortOrder.getSortKeys().length]; System.arraycopy(dataBytes, entryIDs.length * 8 + 4, valuesBytes, 0, valuesBytes.length); int valuesBytesOffset = entryIDs.length * 8 + 4; int valuesBytesLength = dataBytes.length - valuesBytesOffset; valuesBytes = new byte[valuesBytesLength]; System.arraycopy(dataBytes, valuesBytesOffset, valuesBytes, 0, valuesBytesLength); this.valuesBytesOffsets = null; } private SortValuesSet(long[] entryIDs, byte[] keyBytes, byte[] valuesBytes, VLVIndex vlvIndex, ID2Entry id2entry) { this.keyBytes = keyBytes; this.id2entry = id2entry; this.entryIDs = entryIDs; this.valuesBytes = valuesBytes; this.vlvIndex = vlvIndex; this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>(); } private SortValuesSet() {} /** * Add the given entryID and values from this VLV idnex. @@ -137,10 +117,14 @@ entryIDs = new long[1]; entryIDs[0] = entryID; valuesBytes = attributeValuesToDatabase(values); if(valuesBytesOffsets != null) { valuesBytesOffsets = new int[1]; valuesBytesOffsets[0] = 0; } return true; } if(vlvIndex.comparator.compareValuesInSet(this, entryIDs.length - 1, entryID, if(vlvIndex.comparator.compare(this, entryIDs.length - 1, entryID, values) < 0) { long[] updatedEntryIDs = new long[entryIDs.length + 1]; @@ -156,6 +140,17 @@ valuesBytes.length, newValuesBytes.length); if(valuesBytesOffsets != null) { int[] updatedValuesBytesOffsets = new int[valuesBytesOffsets.length + 1]; System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets, 0, valuesBytesOffsets.length); updatedValuesBytesOffsets[valuesBytesOffsets.length] = updatedValuesBytes.length - newValuesBytes.length; valuesBytesOffsets = updatedValuesBytesOffsets; } entryIDs = updatedEntryIDs; valuesBytes = updatedValuesBytes; return true; @@ -186,7 +181,7 @@ updatedEntryIDs[pos] = entryID; byte[] newValuesBytes = attributeValuesToDatabase(values); int valuesPos = pos * newValuesBytes.length; int valuesPos = valuesBytesOffsets[pos]; byte[] updatedValuesBytes = new byte[valuesBytes.length + newValuesBytes.length]; System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, valuesPos); @@ -196,6 +191,22 @@ System.arraycopy(newValuesBytes, 0, updatedValuesBytes, valuesPos, newValuesBytes.length); if(valuesBytesOffsets != null) { int[] updatedValuesBytesOffsets = new int[valuesBytesOffsets.length + 1]; System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets, 0, pos); // Update the rest of the offsets one by one - Expensive! for(int i = pos; i < valuesBytesOffsets.length; i++) { updatedValuesBytesOffsets[i+1] = valuesBytesOffsets[i] + newValuesBytes.length; } updatedValuesBytesOffsets[pos] = valuesBytesOffsets[pos]; valuesBytesOffsets = updatedValuesBytesOffsets; } entryIDs = updatedEntryIDs; valuesBytes = updatedValuesBytes; } @@ -222,6 +233,11 @@ return false; } if(valuesBytesOffsets == null) { updateValuesBytesOffsets(); } int pos = binarySearch(entryID, values); if(pos < 0) { @@ -235,54 +251,93 @@ System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, pos); System.arraycopy(entryIDs, pos+1, updatedEntryIDs, pos, entryIDs.length-pos-1); int valuesLength = ENCODED_ATTRIBUTE_VALUE_SIZE * vlvIndex.sortOrder.getSortKeys().length; int valuesPos = pos * valuesLength; int valuesLength; int valuesPos = valuesBytesOffsets[pos]; if(pos < valuesBytesOffsets.length - 1) { valuesLength = valuesBytesOffsets[pos+1] - valuesPos; } else { valuesLength = valuesBytes.length - valuesPos; } byte[] updatedValuesBytes = new byte[valuesBytes.length - valuesLength]; System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, valuesPos); System.arraycopy(valuesBytes, valuesPos + valuesLength, updatedValuesBytes, valuesPos, valuesBytes.length - valuesPos - valuesLength); int[] updatedValuesBytesOffsets = new int[valuesBytesOffsets.length - 1]; System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets, 0, pos); // Update the rest of the offsets one by one - Expensive! for(int i = pos + 1; i < valuesBytesOffsets.length; i++) { updatedValuesBytesOffsets[i-1] = valuesBytesOffsets[i] - valuesLength; } entryIDs = updatedEntryIDs; valuesBytes = updatedValuesBytes; valuesBytesOffsets = updatedValuesBytesOffsets; return true; } } /** * Split portions of this set into another set. The values of the new set is * from the front of this set. * from the end of this set. * * @param splitLength The size of the new set. * @return The split set. */ public SortValuesSet split(int splitLength) { if(valuesBytesOffsets == null) { updateValuesBytesOffsets(); } long[] splitEntryIDs = new long[splitLength]; byte[] splitValuesBytes = new byte[splitLength * ENCODED_ATTRIBUTE_VALUE_SIZE * vlvIndex.sortOrder.getSortKeys().length]; byte[] splitValuesBytes = new byte[valuesBytes.length - valuesBytesOffsets[valuesBytesOffsets.length - splitLength]]; int[] splitValuesBytesOffsets = new int[splitLength]; long[] updatedEntryIDs = new long[entryIDs.length - splitEntryIDs.length]; System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, updatedEntryIDs.length); System.arraycopy(entryIDs, updatedEntryIDs.length, splitEntryIDs, 0, splitEntryIDs.length); byte[] updatedValuesBytes = new byte[valuesBytes.length - splitValuesBytes.length]; byte[] updatedValuesBytes = new byte[valuesBytesOffsets[valuesBytesOffsets.length - splitLength]]; System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, updatedValuesBytes.length); System.arraycopy(valuesBytes, updatedValuesBytes.length, splitValuesBytes, 0, splitValuesBytes.length); SortValuesSet splitValuesSet = new SortValuesSet(splitEntryIDs, keyBytes, splitValuesBytes, vlvIndex, id2entry); int[] updatedValuesBytesOffsets = new int[valuesBytesOffsets.length - splitValuesBytesOffsets.length]; System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets, 0, updatedValuesBytesOffsets.length); for(int i = updatedValuesBytesOffsets.length; i < valuesBytesOffsets.length; i++) { splitValuesBytesOffsets[i - updatedValuesBytesOffsets.length] = valuesBytesOffsets[i] - valuesBytesOffsets[updatedValuesBytesOffsets.length]; } SortValuesSet splitValuesSet = new SortValuesSet(); splitValuesSet.entryIDs = splitEntryIDs; splitValuesSet.keyBytes = this.keyBytes; splitValuesSet.valuesBytes = splitValuesBytes; splitValuesSet.valuesBytesOffsets = splitValuesBytesOffsets; splitValuesSet.vlvIndex = this.vlvIndex; entryIDs = updatedEntryIDs; valuesBytes = updatedValuesBytes; valuesBytesOffsets = updatedValuesBytesOffsets; keyBytes = null; return splitValuesSet; @@ -369,7 +424,7 @@ for(int j = entryIDs.length - 1; i <= j;) { int k = i + j >> 1; int l = vlvIndex.comparator.compareValuesInSet(this, k, entryID, values); int l = vlvIndex.comparator.compare(this, k, entryID, values); if(l < 0) i = k + 1; else @@ -407,71 +462,38 @@ return entryIDs; } private static int encodedLengthSize(int length) { if ((length & 0x000000FF) == length) { return 1; } else if ((length & 0x0000FFFF) == length) { return 2; } else if ((length & 0x00FFFFFF) == length) { return 3; } else { return 4; } } private byte[] attributeValuesToDatabase(AttributeValue[] values) throws DirectoryException { byte[] valuesBytes = new byte[values.length * (ENCODED_ATTRIBUTE_VALUE_SIZE)]; for(int i = 0; i < values.length; i++) int totalValueBytes = 0; LinkedList<byte[]> valueBytes = new LinkedList<byte[]>(); for (AttributeValue v : values) { AttributeValue value = values[i]; int length; byte[] lengthBytes = new byte[ENCODED_VALUE_LENGTH_SIZE]; if(value == null) byte[] vBytes; if(v == null) { length = 0; vBytes = new byte[0]; } else { byte[] valueBytes = value.getNormalizedValueBytes(); length = valueBytes.length; if(valueBytes.length > ENCODED_VALUE_SIZE) { System.arraycopy(valueBytes, 0, valuesBytes, i * ENCODED_ATTRIBUTE_VALUE_SIZE + ENCODED_VALUE_LENGTH_SIZE, ENCODED_VALUE_SIZE); } else { System.arraycopy(valueBytes, 0, valuesBytes, i * ENCODED_ATTRIBUTE_VALUE_SIZE + ENCODED_VALUE_LENGTH_SIZE, valueBytes.length); } vBytes = v.getNormalizedValueBytes(); } for (int j = ENCODED_VALUE_LENGTH_SIZE - 1; j >= 0; j--) { lengthBytes[j] = (byte) (length & 0xFF); length >>>= 8; } System.arraycopy(lengthBytes, 0, valuesBytes, i * (ENCODED_ATTRIBUTE_VALUE_SIZE), lengthBytes.length); byte[] vLength = ASN1Element.encodeLength(vBytes.length); valueBytes.add(vLength); valueBytes.add(vBytes); totalValueBytes += vLength.length + vBytes.length; } return valuesBytes; byte[] attrBytes = new byte[totalValueBytes]; int pos = 0; for (byte[] b : valueBytes) { System.arraycopy(b, 0, attrBytes, pos, b.length); pos += b.length; } return attrBytes; } /** @@ -496,18 +518,22 @@ return keyBytes; } SortKey[] sortKeys = vlvIndex.sortOrder.getSortKeys(); int numValues = sortKeys.length; AttributeValue[] values = new AttributeValue[numValues]; for (int i = (entryIDs.length - 1) * numValues, j = 0; i < entryIDs.length * numValues; i++, j++) if(valuesBytesOffsets == null) { values[j] = new AttributeValue(sortKeys[j].getAttributeType(), new ASN1OctetString(getValue(i))); updateValuesBytesOffsets(); } keyBytes = vlvIndex.encodeKey(entryIDs[entryIDs.length - 1], values); int vBytesPos = valuesBytesOffsets[valuesBytesOffsets.length - 1]; int vBytesLength = valuesBytes.length - vBytesPos; byte[] idBytes = JebFormat.entryIDToDatabase(entryIDs[entryIDs.length - 1]); keyBytes = new byte[vBytesLength + idBytes.length]; System.arraycopy(valuesBytes, vBytesPos, keyBytes, 0, vBytesLength); System.arraycopy(idBytes, 0, keyBytes, vBytesLength, idBytes.length); return keyBytes; } @@ -587,6 +613,34 @@ return new SortValues(id, values, vlvIndex.sortOrder); } private void updateValuesBytesOffsets() { valuesBytesOffsets = new int[entryIDs.length]; int vBytesPos = 0; int numAttributes = vlvIndex.sortOrder.getSortKeys().length; for(int pos = 0; pos < entryIDs.length; pos++) { valuesBytesOffsets[pos] = vBytesPos; for(int i = 0; i < numAttributes; i++) { int valueLength = valuesBytes[vBytesPos] & 0x7F; if (valueLength != valuesBytes[vBytesPos++]) { int valueLengthBytes = valueLength; valueLength = 0; for (int j=0; j < valueLengthBytes; j++, vBytesPos++) { valueLength = (valueLength << 8) | (valuesBytes[vBytesPos] & 0xFF); } } vBytesPos += valueLength; } } } /** * Retrieve an attribute value from this values set. The index is the * absolute index. (ie. for a sort on 3 attributes per entry, an vlvIndex of 6 @@ -601,52 +655,47 @@ public byte[] getValue(int index) throws JebException, DatabaseException, DirectoryException { // If values bytes is null, we have to get the value by getting the // entry by ID and getting the value. if(valuesBytes != null) if(valuesBytesOffsets == null) { int pos = index * ENCODED_ATTRIBUTE_VALUE_SIZE; int length = 0; byte[] valueBytes; for (int k = 0; k < ENCODED_VALUE_LENGTH_SIZE; k++, pos++) updateValuesBytesOffsets(); } int numAttributes = vlvIndex.sortOrder.getSortKeys().length; int vIndex = index / numAttributes; int vOffset = index % numAttributes; int vBytesPos = valuesBytesOffsets[vIndex]; // Find the desired value in the sort order set. for(int i = 0; i <= vOffset; i++) { int valueLength = valuesBytes[vBytesPos] & 0x7F; if (valueLength != valuesBytes[vBytesPos++]) { length <<= 8; length |= (valuesBytes[pos] & 0xFF); int valueLengthBytes = valueLength; valueLength = 0; for (int j=0; j < valueLengthBytes; j++, vBytesPos++) { valueLength = (valueLength << 8) | (valuesBytes[vBytesPos] & 0xFF); } } if(length == 0) if(i == vOffset) { return null; if(valueLength == 0) { return null; } else { byte[] valueBytes = new byte[valueLength]; System.arraycopy(valuesBytes, vBytesPos, valueBytes, 0, valueLength); return valueBytes; } } // If the value has exceeded the max value size, we have to get the // value by getting the entry by ID. else if(length <= ENCODED_VALUE_SIZE && length > 0) else { valueBytes = new byte[length]; System.arraycopy(valuesBytes, pos, valueBytes, 0, length); return valueBytes; vBytesPos += valueLength; } } if(id2entry == null) { return new byte[0]; } // Get the entry from id2entry and assign the values from the entry. // Once the values are assigned from the retrieved entry, it will // not be retrieve again from future compares. EntryID id = new EntryID(entryIDs[index / vlvIndex.sortOrder.getSortKeys().length]); AttributeValue[] values = cachedAttributeValues.get(id); if(values == null) { values = vlvIndex.getSortValues(id2entry.get(null, id)); cachedAttributeValues.put(id, values); } int offset = index % values.length; return values[offset].getNormalizedValueBytes(); return new byte[0]; } } opends/src/server/org/opends/server/backends/jeb/VLVIndex.java
@@ -52,15 +52,13 @@ import org.opends.server.api.OrderingMatchingRule; import org.opends.server.config.ConfigException; import org.opends.server.protocols.asn1.ASN1Element; import org.opends.server.protocols.asn1.ASN1OctetString; import org.opends.server.protocols.ldap.LDAPResultCode; import org.opends.server.controls.VLVRequestControl; import org.opends.server.controls.VLVResponseControl; import org.opends.server.controls.ServerSideSortRequestControl; import java.util.List; import java.util.LinkedList; import java.util.Iterator; import java.util.ArrayList; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** @@ -202,8 +200,7 @@ if(attrType == null) { Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( String.valueOf(sortKeys[i]), name); ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(sortAttrs[i], name); throw new ConfigException(msg); } sortKeys[i] = new SortKey(attrType, ascending[i]); @@ -323,6 +320,53 @@ } /** * Update the vlvIndex for a new entry. * * @param buffer The index buffer to buffer the changes. * @param entryID The entry ID. * @param entry The entry to be indexed. * @return True if the entry ID for the entry are added. False if * the entry ID already exists. * @throws DirectoryException If a Directory Server * error occurs. */ public boolean addEntry(IndexBuffer buffer, EntryID entryID, Entry entry) throws DirectoryException { DN entryDN = entry.getDN(); if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry)) { SortValues sortValues = new SortValues(entryID, entry, sortOrder); IndexBuffer.BufferedVLVValues bufferedValues = buffer.getVLVIndex(this); if(bufferedValues == null) { bufferedValues = new IndexBuffer.BufferedVLVValues(); buffer.putBufferedVLVIndex(this, bufferedValues); } if(bufferedValues.deletedValues != null && bufferedValues.deletedValues.contains(sortValues)) { bufferedValues.deletedValues.remove(sortValues); return true; } TreeSet<SortValues> bufferedAddedValues = bufferedValues.addedValues; if(bufferedAddedValues == null) { bufferedAddedValues = new TreeSet<SortValues>(); bufferedValues.addedValues = bufferedAddedValues; } bufferedAddedValues.add(sortValues); return true; } return false; } /** * Update the vlvIndex for a deleted entry. * * @param txn The database transaction to be used for the deletions @@ -346,6 +390,52 @@ } /** * Update the vlvIndex for a deleted entry. * * @param buffer The database transaction to be used for the deletions * @param entryID The entry ID * @param entry The contents of the deleted entry. * @return True if the entry was successfully removed from this VLV index * or False otherwise. * @throws DirectoryException If a Directory Server error occurs. */ public boolean removeEntry(IndexBuffer buffer, EntryID entryID, Entry entry) throws DirectoryException { DN entryDN = entry.getDN(); if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry)) { SortValues sortValues = new SortValues(entryID, entry, sortOrder); IndexBuffer.BufferedVLVValues bufferedValues = buffer.getVLVIndex(this); if(bufferedValues == null) { bufferedValues = new IndexBuffer.BufferedVLVValues(); buffer.putBufferedVLVIndex(this, bufferedValues); } if(bufferedValues.addedValues != null && bufferedValues.addedValues.contains(sortValues)) { bufferedValues.addedValues.remove(sortValues); return true; } TreeSet<SortValues> bufferedDeletedValues = bufferedValues.deletedValues; if(bufferedDeletedValues == null) { bufferedDeletedValues = new TreeSet<SortValues>(); bufferedValues.deletedValues = bufferedDeletedValues; } bufferedDeletedValues.add(sortValues); return true; } return false; } /** * Update the vlvIndex to reflect a sequence of modifications in a Modify * operation. * @@ -440,6 +530,100 @@ } /** * Update the vlvIndex to reflect a sequence of modifications in a Modify * operation. * * @param buffer The database transaction to be used for the deletions * @param entryID The ID of the entry that was modified. * @param oldEntry The entry before the modifications were applied. * @param newEntry The entry after the modifications were applied. * @param mods The sequence of modifications in the Modify operation. * @return True if the modification was successfully processed or False * otherwise. * @throws JebException If an error occurs during an operation on a * JE database. * @throws DatabaseException If an error occurs during an operation on a * JE database. * @throws DirectoryException If a Directory Server error occurs. */ public boolean modifyEntry(IndexBuffer buffer, EntryID entryID, Entry oldEntry, Entry newEntry, List<Modification> mods) throws DatabaseException, DirectoryException, JebException { DN oldEntryDN = oldEntry.getDN(); DN newEntryDN = newEntry.getDN(); if(oldEntryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(oldEntry)) { if(newEntryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(newEntry)) { // The entry should still be indexed. See if any sorted attributes are // changed. boolean sortAttributeModified = false; SortKey[] sortKeys = sortOrder.getSortKeys(); for(SortKey sortKey : sortKeys) { AttributeType attributeType = sortKey.getAttributeType(); Iterable<AttributeType> subTypes = DirectoryServer.getSchema().getSubTypes(attributeType); for(Modification mod : mods) { AttributeType modAttrType = mod.getAttribute().getAttributeType(); if(modAttrType.equals(attributeType)) { sortAttributeModified = true; break; } for(AttributeType subType : subTypes) { if(modAttrType.equals(subType)) { sortAttributeModified = true; break; } } } if(sortAttributeModified) { break; } } if(sortAttributeModified) { boolean success; // Sorted attributes have changed. Reindex the entry; success = removeEntry(buffer, entryID, oldEntry); success &= addEntry(buffer, entryID, newEntry); return success; } } else { // The modifications caused the new entry to be unindexed. Remove from // vlvIndex. return removeEntry(buffer, entryID, oldEntry); } } else { if(newEntryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(newEntry)) { // The modifications caused the new entry to be indexed. Add to // vlvIndex. return addEntry(buffer, entryID, newEntry); } } // The modifications does not affect this vlvIndex return true; } /** * Put a sort values set in this VLV index. * * @param txn The transaction to use when retriving the set or NULL if it is @@ -503,7 +687,7 @@ TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " + "Creating unbound set.", config.getName()); } sortValuesSet = new SortValuesSet(this, id2entry); sortValuesSet = new SortValuesSet(this); } else { @@ -520,7 +704,7 @@ foundKeyHex); } sortValuesSet = new SortValuesSet(key.getData(), data.getData(), this, id2entry); this); } } finally @@ -590,7 +774,7 @@ TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " + "Creating unbound set.", config.getName()); } sortValuesSet = new SortValuesSet(this, id2entry); sortValuesSet = new SortValuesSet(this); key.setData(new byte[0]); } else @@ -608,7 +792,7 @@ foundKeyHex); } sortValuesSet = new SortValuesSet(key.getData(), data.getData(), this, id2entry); this); } @@ -643,6 +827,7 @@ byte[] after = sortValuesSet.toDatabase(); data.setData(after); put(txn, key, data); // TODO: What about phantoms? } if(success) @@ -690,7 +875,7 @@ foundKeyHex); } sortValuesSet = new SortValuesSet(key.getData(), data.getData(), this, id2entry); this); boolean success = sortValuesSet.remove(entryID, values); byte[] after = sortValuesSet.toDatabase(); data.setData(after); @@ -710,6 +895,223 @@ } /** * Update the vlvIndex with the specified values to add and delete. * * @param txn A database transaction, or null if none is required. * @param addedValues The values to add to the VLV index. * @param deletedValues The values to delete from the VLV index. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a Directory Server * error occurs. * @throws JebException If an error occurs in the JE backend. */ public void updateIndex(Transaction txn, TreeSet<SortValues> addedValues, TreeSet<SortValues> deletedValues) throws DirectoryException, DatabaseException, JebException { // Handle cases where nothing is changed early to avoid // DB access. if((addedValues == null || addedValues.size() == 0) && (deletedValues == null || deletedValues.size() == 0)) { return; } DatabaseEntry key = new DatabaseEntry(); OperationStatus status; LockMode lockMode = LockMode.RMW; DatabaseEntry data = new DatabaseEntry(); SortValuesSet sortValuesSet; Iterator<SortValues> aValues = null; Iterator<SortValues> dValues = null; SortValues av = null; SortValues dv = null; if(addedValues != null) { aValues = addedValues.iterator(); av = aValues.next(); } if(deletedValues != null) { dValues = deletedValues.iterator(); dv = dValues.next(); } while(true) { if(av != null) { if(dv != null) { // Start from the smallest values from either set. if(av.compareTo(dv) < 0) { key.setData(encodeKey(av.getEntryID(), av.getValues())); } else { key.setData(encodeKey(dv.getEntryID(), dv.getValues())); } } else { key.setData(encodeKey(av.getEntryID(), av.getValues())); } } else if(dv != null) { key.setData(encodeKey(dv.getEntryID(), dv.getValues())); } else { break; } Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED); try { status = cursor.getSearchKeyRange(key, data,lockMode); } finally { cursor.close(); } if(status != OperationStatus.SUCCESS) { // There are no records in the database if(debugEnabled()) { TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " + "Creating unbound set.", config.getName()); } sortValuesSet = new SortValuesSet(this); key.setData(new byte[0]); } else { if(debugEnabled()) { StringBuilder searchKeyHex = new StringBuilder(); StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4); StringBuilder foundKeyHex = new StringBuilder(); StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4); TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " + "%s\nSearch Key:%s\nFound Key:%s\n", config.getName(), searchKeyHex, foundKeyHex); } sortValuesSet = new SortValuesSet(key.getData(), data.getData(), this); } int oldSize = sortValuesSet.size(); if(key.getData().length == 0) { // This is the last unbounded set. while(av != null) { sortValuesSet.add(av.getEntryID(), av.getValues()); aValues.remove(); if(aValues.hasNext()) { av = aValues.next(); } else { av = null; } } while(dv != null) { sortValuesSet.remove(dv.getEntryID(), dv.getValues()); dValues.remove(); if(dValues.hasNext()) { dv = dValues.next(); } else { dv = null; } } } else { SortValues maxValues = decodeKey(sortValuesSet.getKeyBytes()); while(av != null && av.compareTo(maxValues) <= 0) { sortValuesSet.add(av.getEntryID(), av.getValues()); aValues.remove(); if(aValues.hasNext()) { av = aValues.next(); } else { av = null; } } while(dv != null && dv.compareTo(maxValues) <= 0) { sortValuesSet.remove(dv.getEntryID(), dv.getValues()); dValues.remove(); if(dValues.hasNext()) { dv = dValues.next(); } else { dv = null; } } } int newSize = sortValuesSet.size(); if(newSize >= sortedSetCapacity) { SortValuesSet splitSortValuesSet = sortValuesSet.split(newSize / 2); byte[] splitAfter = splitSortValuesSet.toDatabase(); key.setData(splitSortValuesSet.getKeyBytes()); data.setData(splitAfter); put(txn, key, data); byte[] after = sortValuesSet.toDatabase(); key.setData(sortValuesSet.getKeyBytes()); data.setData(after); put(txn, key, data); if(debugEnabled()) { TRACER.debugInfo("SortValuesSet with key %s has reached" + " the entry size of %d. Spliting into two sets with " + " keys %s and %s.", splitSortValuesSet.getKeySortValues(), newSize, sortValuesSet.getKeySortValues(), splitSortValuesSet.getKeySortValues()); } } else if(newSize == 0) { delete(txn, key); } else { byte[] after = sortValuesSet.toDatabase(); data.setData(after); put(txn, key, data); } count.getAndAdd(newSize - oldSize); } } /** * Evaluate a search with sort control using this VLV index. * * @param txn The transaction to used when reading the index or NULL if it is @@ -924,8 +1326,7 @@ foundKeyHex); } SortValuesSet sortValuesSet = new SortValuesSet(key.getData(), data.getData(), this, id2entry); new SortValuesSet(key.getData(), data.getData(), this); AttributeValue[] assertionValue = new AttributeValue[1]; assertionValue[0] = new AttributeValue( @@ -1213,6 +1614,66 @@ } /** * Decode a VLV database key. * * @param keyBytes The byte array to decode. * @return The sort values represented by the key bytes. * @throws DirectoryException If a Directory Server error occurs. */ SortValues decodeKey(byte[] keyBytes) throws DirectoryException { if(keyBytes == null || keyBytes.length == 0) { return null; } AttributeValue[] attributeValues = new AttributeValue[sortOrder.getSortKeys().length]; int vBytesPos = 0; for(int i = 0; i < attributeValues.length; i++) { int valueLength = keyBytes[vBytesPos] & 0x7F; if (valueLength != keyBytes[vBytesPos++]) { int valueLengthBytes = valueLength; valueLength = 0; for (int j=0; j < valueLengthBytes; j++, vBytesPos++) { valueLength = (valueLength << 8) | (keyBytes[vBytesPos] & 0xFF); } } if(valueLength == 0) { attributeValues[i] = null; } else { byte[] valueBytes = new byte[valueLength]; System.arraycopy(keyBytes, vBytesPos, valueBytes, 0, valueLength); attributeValues[i] = new AttributeValue(sortOrder.getSortKeys()[i].getAttributeType(), new ASN1OctetString(valueBytes)); } vBytesPos += valueLength; } // FIXME: Should pos+offset method for decoding IDs be added to // JebFormat? long v = 0; for (int i = vBytesPos; i < keyBytes.length; i++) { v <<= 8; v |= (keyBytes[i] & 0xFF); } return new SortValues(new EntryID(v), attributeValues, sortOrder); } /** * Get the sorted set capacity configured for this VLV index. * * @return The sorted set capacity. @@ -1298,7 +1759,7 @@ if(attrType == null) { Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( String.valueOf(sortKeys[i]), name); sortAttrs[i], name); unacceptableReasons.add(msg); return false; } opends/src/server/org/opends/server/backends/jeb/VLVKeyComparator.java
@@ -203,7 +203,7 @@ // If we've gotten here, then we can't tell a difference between the sets // of available values, so sort based on entry ID if its in the key. if(b1Pos + 8 < b1.length && b2Pos + 8 < b2.length) if(b1Pos + 8 <= b1.length && b2Pos + 8 <= b2.length) { long b1ID = 0; for (int i = b1Pos; i < b1Pos + 8; i++) @@ -273,7 +273,7 @@ * not acceptable for use with the * associated equality matching rule). */ public int compareValuesInSet(SortValuesSet set, int index, public int compare(SortValuesSet set, int index, long entryID, AttributeValue[] values) throws JebException, DatabaseException, DirectoryException { opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
@@ -615,7 +615,7 @@ Entry entry; try { entry = id2entry.get(null, entryID); entry = id2entry.get(null, entryID, LockMode.DEFAULT); } catch (Exception e) { @@ -725,7 +725,7 @@ Entry entry; try { entry = id2entry.get(null, entryID); entry = id2entry.get(null, entryID, LockMode.DEFAULT); } catch (Exception e) { @@ -753,7 +753,7 @@ Entry childEntry; try { childEntry = id2entry.get(null, id); childEntry = id2entry.get(null, id, LockMode.DEFAULT); } catch (Exception e) { @@ -866,7 +866,7 @@ Entry entry; try { entry = id2entry.get(null, entryID); entry = id2entry.get(null, entryID, LockMode.DEFAULT); } catch (Exception e) { @@ -894,7 +894,7 @@ Entry subordEntry; try { subordEntry = id2entry.get(null, id); subordEntry = id2entry.get(null, id, LockMode.DEFAULT); } catch (Exception e) { @@ -1022,8 +1022,7 @@ while(status == OperationStatus.SUCCESS) { SortValuesSet sortValuesSet = new SortValuesSet(key.getData(), data.getData(), vlvIndex, id2entry); new SortValuesSet(key.getData(), data.getData(), vlvIndex); for(int i = 0; i < sortValuesSet.getEntryIDs().length; i++) { keyCount++; @@ -1065,7 +1064,7 @@ EntryID id = new EntryID(values.getEntryID()); try { entry = id2entry.get(null, id); entry = id2entry.get(null, id, LockMode.DEFAULT); } catch (Exception e) { @@ -1279,7 +1278,7 @@ Entry entry; try { entry = id2entry.get(null, id); entry = id2entry.get(null, id, LockMode.DEFAULT); } catch (Exception e) { @@ -1400,7 +1399,7 @@ // Check the ID is in dn2id with the correct DN. try { EntryID id = dn2id.get(null, dn); EntryID id = dn2id.get(null, dn, LockMode.DEFAULT); if (id == null) { if (debugEnabled()) @@ -1441,7 +1440,7 @@ { try { EntryID id = dn2id.get(null, parentDN); EntryID id = dn2id.get(null, parentDN, LockMode.DEFAULT); if (id == null) { if (debugEnabled()) @@ -1483,7 +1482,7 @@ EntryID parentID = null; try { parentID = dn2id.get(null, parentDN); parentID = dn2id.get(null, parentDN, LockMode.DEFAULT); if (parentID == null) { if (debugEnabled()) @@ -1555,7 +1554,7 @@ EntryID id = null; try { id = dn2id.get(null, dn); id = dn2id.get(null, dn, LockMode.DEFAULT); if (id == null) { if (debugEnabled()) opends/src/server/org/opends/server/backends/jeb/importLDIF/DNContext.java
@@ -35,6 +35,7 @@ import com.sleepycat.je.DatabaseException; import com.sleepycat.je.Transaction; import com.sleepycat.je.LockMode; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; @@ -468,7 +469,7 @@ return null; } } parentID = dn2id.get(txn, parentDN); parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT); //If the parent is in dn2id, add it to the cache. if (parentID != null) { synchronized(synchObject) { opends/src/server/org/opends/server/backends/jeb/importLDIF/Importer.java
@@ -829,7 +829,9 @@ DN dn = DN.decode(new ASN1OctetString(key.getData())); if(!context.getIncludeBranches().contains(dn)) { EntryID id = new EntryID(data); Entry entry = srcEntryContainer.getID2Entry().get(null, id); Entry entry = srcEntryContainer.getID2Entry().get(null, id, LockMode.DEFAULT); processEntry(context, entry); migratedCount++; status = cursor.getNext(key, data, lockMode); @@ -909,7 +911,8 @@ throw new JebException(message); } EntryID id = new EntryID(data); Entry entry = srcEntryContainer.getID2Entry().get(null, id); Entry entry = srcEntryContainer.getID2Entry().get(null, id, LockMode.DEFAULT); processEntry(importContext, entry); migratedCount++; status = cursor.getNext(key, data, lockMode); opends/src/server/org/opends/server/backends/jeb/importLDIF/WorkThread.java
@@ -39,6 +39,7 @@ import java.util.*; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.Transaction; import com.sleepycat.je.LockMode; /** * A thread to process import entries from a queue. Multiple instances of @@ -410,9 +411,9 @@ private EntryID getAncestorID(DN2ID dn2id, DN dn, Transaction txn) throws DatabaseException { int i=0; EntryID nodeID = dn2id.get(txn, dn); EntryID nodeID = dn2id.get(txn, dn, LockMode.DEFAULT); if(nodeID == null) { while((nodeID = dn2id.get(txn, dn)) == null) { while((nodeID = dn2id.get(txn, dn, LockMode.DEFAULT)) == null) { try { Thread.sleep(50); if(i == 3) { @@ -444,12 +445,12 @@ DN2ID dn2id = context.getEntryContainer().getDN2ID(); LDIFImportConfig ldifImportConfig = context.getLDIFImportConfig(); DN entryDN = entry.getDN(); EntryID entryID = dn2id.get(txn, entryDN); EntryID entryID = dn2id.get(txn, entryDN, LockMode.DEFAULT); if (entryID != null) { if (ldifImportConfig.appendToExistingData() && ldifImportConfig.replaceExistingEntries()) { ID2Entry id2entry = context.getEntryContainer().getID2Entry(); Entry existingEntry = id2entry.get(txn, entryID); Entry existingEntry = id2entry.get(txn, entryID, LockMode.DEFAULT); element.setExistingEntry(existingEntry); } else { Message msg = WARN_JEB_IMPORT_ENTRY_EXISTS.get(); opends/src/server/org/opends/server/core/PluginConfigManager.java
@@ -5066,7 +5066,7 @@ * subordinate entry is associated. * @param oldEntry The subordinate entry prior to the move/rename * operation. * @param newEntry The subordinate enry after the move/rename * @param newEntry The subordinate entry after the move/rename * operation. * @param modifications A list into which any modifications made to the * target entry should be placed. opends/src/server/org/opends/server/tools/DBTest.java
@@ -1460,8 +1460,7 @@ StringBuilder builder = new StringBuilder(); SortValuesSet svs = new SortValuesSet(key.getData(), data.getData(), index, null); index); long[] entryIDs = svs.getEntryIDs(); for(int i = 0; i < entryIDs.length; i++) { opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -1435,3 +1435,13 @@ ds-cfg-base-dn: o=ldif ds-cfg-ldif-file: config/ldif-backend.ldif dn: cn=File-Based Debug Logger,cn=Loggers,cn=config changetype: modify replace: ds-cfg-enabled ds-cfg-enabled: true - replace: ds-cfg-default-include-throwable-cause ds-cfg-default-include-throwable-cause: true - replace: ds-cfg-default-throwable-stack-frames ds-cfg-default-throwable-stack-frames: 500 opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
@@ -37,7 +37,6 @@ import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.protocols.ldap.LDAPFilter; import org.opends.server.protocols.asn1.ASN1OctetString; import org.opends.server.types.*; import org.opends.server.util.Base64; import static @@ -50,6 +49,7 @@ import static org.testng.Assert.*; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.LockMode; import org.opends.server.core.DeleteOperationBasis; import org.opends.server.core.ModifyOperationBasis; @@ -819,7 +819,7 @@ { entry = ec.getEntry(DN.decode("uid=user.539,ou=People,dc=test,dc=com")); entryID = ec.getDN2ID().get(null, DN.decode("uid=user.539,ou=People,dc=test,dc=com")); DN.decode("uid=user.539,ou=People,dc=test,dc=com"), LockMode.DEFAULT); DeleteOperationBasis delete = new DeleteOperationBasis(conn, conn.nextOperationID(), @@ -835,7 +835,7 @@ assertFalse(ec.entryExists(DN.decode("uid=user.539,ou=People,dc=test,dc=com"))); assertNull(ec.getDN2ID().get(null, DN.decode("uid=user.539,ou=People,dc=test,dc=com"))); DN.decode("uid=user.539,ou=People,dc=test,dc=com"), LockMode.DEFAULT)); assertFalse(ec.getDN2URI().delete(null, DN.decode("uid=user.539,ou=People,dc=test,dc=com"))); @@ -919,7 +919,7 @@ entry = ec.getEntry(DN.decode("uid=user.0,ou=People,dc=test,dc=com")); oldEntry = entries.get(0); entryID = ec.getDN2ID().get(null, DN.decode("uid=user.0,ou=People,dc=test,dc=com")); DN.decode("uid=user.0,ou=People,dc=test,dc=com"), LockMode.DEFAULT); assertNotNull(entry); LinkedHashSet<AttributeValue> values = @@ -1086,7 +1086,7 @@ newEntry = entries.get(1); newEntry.applyModifications(modifications); entry = ec.getEntry(DN.decode("uid=user.1,ou=People,dc=test,dc=com")); entryID = ec.getDN2ID().get(null, DN.decode("uid=user.1,ou=People,dc=test,dc=com")); entryID = ec.getDN2ID().get(null, DN.decode("uid=user.1,ou=People,dc=test,dc=com"), LockMode.DEFAULT); assertNotNull(entryID); @@ -1290,12 +1290,12 @@ entry, null); assertNotNull(backend.getEntry(DN.decode("cn=Abbey Abbie,ou=People,dc=test,dc=com"))); assertNotNull(ec.getDN2ID().get(null, DN.decode("cn=Abbey Abbie,ou=People,dc=test,dc=com"))); assertNotNull(ec.getDN2ID().get(null, DN.decode("cn=Abbey Abbie,ou=People,dc=test,dc=com"), LockMode.DEFAULT)); assertNull(backend.getEntry(DN.decode("uid=user.2,ou=People,dc=test,dc=com"))); assertNull(ec.getDN2ID().get(null, DN.decode("uid=user.2,ou=People,dc=test,dc=com"))); DN.decode("uid=user.2,ou=People,dc=test,dc=com"), LockMode.DEFAULT)); } finally { @@ -1317,9 +1317,9 @@ ec.sharedLock.lock(); try { EntryID newSuperiorID = ec.getDN2ID().get(null, DN.decode("ou=JEB Testers,dc=test,dc=com")); EntryID newSuperiorID = ec.getDN2ID().get(null, DN.decode("ou=JEB Testers,dc=test,dc=com"), LockMode.DEFAULT); EntryID oldID = ec.getDN2ID().get(null, DN.decode("ou=People,dc=test,dc=com")); DN.decode("ou=People,dc=test,dc=com"), LockMode.DEFAULT); assertTrue(newSuperiorID.compareTo(oldID) > 0); ArrayList<Control> noControls = new ArrayList<Control>(0); @@ -1338,17 +1338,17 @@ modifyDN.run(); assertNotNull(backend.getEntry(DN.decode("ou=Good People,ou=JEB Testers,dc=test,dc=com"))); EntryID newID = ec.getDN2ID().get(null, DN.decode("ou=Good People,ou=JEB Testers,dc=test,dc=com")); EntryID newID = ec.getDN2ID().get(null, DN.decode("ou=Good People,ou=JEB Testers,dc=test,dc=com"), LockMode.DEFAULT); assertNotNull(newID); assertTrue(newID.compareTo(newSuperiorID) > 0); assertNotNull(backend.getEntry(DN.decode("uid=user.0,ou=Good People,ou=JEB Testers,dc=test,dc=com"))); EntryID newSubordinateID = ec.getDN2ID().get(null, DN.decode("uid=user.0,ou=Good People,ou=JEB Testers,dc=test,dc=com")); DN.decode("uid=user.0,ou=Good People,ou=JEB Testers,dc=test,dc=com"), LockMode.DEFAULT); assertTrue(newSubordinateID.compareTo(newID) > 0); assertNull(backend.getEntry(DN.decode("ou=People,dc=test,dc=com"))); assertNull(ec.getDN2ID().get(null, DN.decode("ou=People,dc=test,dc=com"))); DN.decode("ou=People,dc=test,dc=com"), LockMode.DEFAULT)); } finally { opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java
@@ -39,11 +39,10 @@ import org.testng.annotations.Test; import static org.testng.Assert.*; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseConfig; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.OperationStatus; import com.sleepycat.je.Transaction; import com.sleepycat.je.LockMode; import java.util.*; @@ -390,7 +389,7 @@ SortValuesSet svs = vlvIndex.getSortValuesSet(null, 0, new AttributeValue[3]); long id = svs.getEntryIDs()[0]; Entry entry = eContainer.getID2Entry().get(null, new EntryID(id)); Entry entry = eContainer.getID2Entry().get(null, new EntryID(id), LockMode.DEFAULT); SortValuesSet svs2 = svs.split(2); svs2.add(id, vlvIndex.getSortValues(entry)); @@ -400,7 +399,7 @@ // Muck up the values of another ID id = svs.getEntryIDs()[0]; entry = eContainer.getID2Entry().get(null, new EntryID(id)); entry = eContainer.getID2Entry().get(null, new EntryID(id), LockMode.DEFAULT); AttributeValue[] values = vlvIndex.getSortValues(entry); AttributeValue[] badValues = new AttributeValue[values.length]; System.arraycopy(values, 1, badValues, 0, values.length - 1); @@ -696,7 +695,7 @@ SortValuesSet svs = vlvIndex.getSortValuesSet(null, 0, new AttributeValue[3]); long id = svs.getEntryIDs()[0]; Entry entry = eContainer.getID2Entry().get(null, new EntryID(id)); Entry entry = eContainer.getID2Entry().get(null, new EntryID(id), LockMode.DEFAULT); svs.remove(id, vlvIndex.getSortValues(entry)); // Add an incorrectly ordered values. @@ -705,7 +704,7 @@ // Muck up the values of another ID id = svs.getEntryIDs()[0]; entry = eContainer.getID2Entry().get(null, new EntryID(id)); entry = eContainer.getID2Entry().get(null, new EntryID(id), LockMode.DEFAULT); AttributeValue[] values = vlvIndex.getSortValues(entry); AttributeValue[] badValues = new AttributeValue[values.length]; System.arraycopy(values, 1, badValues, 0, values.length - 1);