mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

boli
21.08.2008 8c65daf79d1c7fbe47f556c4d4bba2c2859851d1
This patch adds index buffering capabilities to the JE backend as to avoid using a fixed lock timeout for subtree delete and mod DN operations. Previously, any index modifications to subordinate entries of the affected operations will be performed with dn2id and id2entry modifications. This creates multiple random access to index database keys which could cause deadlocks in face of multiple parallel operations. With this fix, all index modifications are buffered up until the end of the operation so that each key of each index will be accessed once and in order. This maintains the DB access ordering in the JE backend of dn2id, id2entry, dn2uri, indexes in config order, VLV indexes in config order, and finally id2children and id2subtree. Since deadlocks should no longer occur in the JE backend, JE lock timeouts are now disabled at the JE environment level instead of the txn level. With this change, the performance of subtree deletes and mod DN operations have increased dramatically.

In order to add buffering capabilities to the VLV index, the format of the index records had to be changed. Previous DBs with VLVs configured will no longer be compatible with this new revision.

Cursors operations for subtree mod DN and delete operations are now taken with READ_COMMITTED JE isolation level to avoid locking un-affected entries and possibility causing deadlocks. Write operations affecting the DN2ID and ID2ENTRY databases will aquire an write lock directly as early as possible to avoid deadlocks.

A issue is also fixed where the debug log genereated during unit tests does not include stack traces.

Fix for issues 2980, 2186, 2979
1 files deleted
22 files modified
3153 ■■■■■ changed files
opends/build.xml 14 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java 184 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java 17 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java 201 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/DN2ID.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java 1296 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java 44 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ID2Entry.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/Index.java 433 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java 8 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/SortValuesSet.java 359 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/VLVIndex.java 489 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/VLVKeyComparator.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/VerifyJob.java 25 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/importLDIF/DNContext.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/importLDIF/Importer.java 7 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/importLDIF/WorkThread.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PluginConfigManager.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/DBTest.java 3 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/resource/config-changes.ldif 10 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java 24 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java 11 ●●●● patch | view | raw | blame | history
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);