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

boli
20.27.2007 7eda83737e5c2a09bef758ac2bcd3b7ea8b32ce3
This refactoring includes the following changes to the JE backend:
- Extracted common interface DatabaseContainer from DN2ID, ID2Entry, etc... classes.
- Moved database read and write methods from EntryContainer to DatabaseContainer.
- Added index configuration to the XML based admin framework.
- Removed redundant configuration objects (Config, IndexConfig).
- Added exclusive/shared lock to EntryContainer. All access to an EntryContainer must acquire a lock before using the internal
DatabaseContainers or making configuration changes.
- Added the ability to add/remove/modify indexes with the backend online. Server will issue rebuild required warning when adding new indexes
or sub-indexes (equality, substring, presence...).
- Added the ability to change the index entry limit for both the backend and each index with the backend online. Server will issue rebuild
required warning if the previous limit has been exceeded.
- Added the ability to change entry compression and index substring length setting while the backend is online.
- Added a persistent state database to each EntryContainer to persist backend configuration between server restarts. Server will issue
rebuild required warning if a new index is added when the backend is offline.
- Added a trusted flag to indexes so that non existent keys will not be interpreted as an empty entry ID set when an index is untrusted. An
index is untrusted when it is added to an non-empty EntryContainer or an inconsistency is detected. Server will issue warning on startup to
rebuild the index.
- Fixed a issue where the LDIF import process stops responding if the temporary import dir is full or unwritable.

Fix for issue 1480 1455 1575
2 files deleted
3 files added
48 files modified
8930 ■■■■ changed files
opends/build.xml 2 ●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 4 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml 11 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/JEIndexConfiguration.xml 208 ●●●●● patch | view | raw | blame | history
opends/src/build-tools/org/opends/build/tools/CoverageDiff.java 6 ●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/Installation.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/DebugLogPublisher.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ApproximateIndexer.java 21 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java 809 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java 638 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BackupManager.java 38 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/Config.java 772 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/DN2ID.java 201 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/DN2URI.java 212 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/DatabaseContainer.java 388 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/DbPreloadComparator.java 37 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java 1057 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EqualityIndexer.java 26 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ExportJob.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ID2Entry.java 148 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ImportContext.java 7 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ImportJob.java 328 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ImportThread.java 69 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/Index.java 411 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/IndexBuilder.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/IndexConfig.java 285 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/IndexMergeThread.java 30 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java 165 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/OrderingIndexer.java 24 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/PresenceIndexer.java 19 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/RebuildJob.java 31 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/RootContainer.java 386 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/State.java 165 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/SubstringIndexer.java 33 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/VerifyJob.java 398 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/LogPublisherErrorHandler.java 18 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/MultifileTextWriter.java 8 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/TimeLimitRotationPolicy.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/BackendMessages.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ConfigMessages.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 10 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/JebMessages.java 83 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/LoggerMessages.java 20 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/monitors/DatabaseEnvironmentMonitor.java 19 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java 6 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplicationDbEnv.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/resource/config-changes.ldif 27 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java 897 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestEntryContainer.java 70 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestImportJob.java 298 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java 507 ●●●●● patch | view | raw | blame | history
opends/build.xml
@@ -1284,7 +1284,7 @@
    </condition>
    <!-- This sets org.opends.test.debug.target if and only if its's not
         alreadly set. -->
         already set. -->
    <condition property="org.opends.test.debug.target"
               value="org.opends.server:level=warning,category=caught|data|database_access|message|protocol">
      <not>
opends/resource/schema/02-config.ldif
@@ -1279,6 +1279,10 @@
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.380 NAME 'ds-sync-conflict'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  USAGE directoryOperation X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.382
  NAME 'ds-cfg-index-substring-length'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
  MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
opends/src/admin/defn/org/opends/server/admin/std/JEBackendConfiguration.xml
@@ -52,6 +52,17 @@
      <ldap:superior>ds-cfg-backend</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:relation name="je-index">
    <adm:one-to-many />
     <adm:profile name="ldap">
      <ldap:rdn-sequence>
        cn=Index
      </ldap:rdn-sequence>
      <ldap:naming-attribute>
        ds-cfg-index-attribute
      </ldap:naming-attribute>
    </adm:profile>
  </adm:relation>
  <adm:property-override name="backend-class">
    <adm:default-behavior>
      <adm:defined>
opends/src/admin/defn/org/opends/server/admin/std/JEIndexConfiguration.xml
New file
@@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
 ! CDDL HEADER START
 !
 ! The contents of this file are subject to the terms of the
 ! Common Development and Distribution License, Version 1.0 only
 ! (the "License").  You may not use this file except in compliance
 ! with the License.
 !
 ! You can obtain a copy of the license at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
 ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 ! See the License for the specific language governing permissions
 ! and limitations under the License.
 !
 ! When distributing Covered Code, include this CDDL HEADER in each
 ! file and include the License file at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 ! add the following below this CDDL HEADER, with the fields enclosed
 ! by brackets "[]" replaced with your own identifying information:
 !      Portions Copyright [yyyy] [name of copyright owner]
 !
 ! CDDL HEADER END
 !
 !
 !      Portions Copyright 2007 Sun Microsystems, Inc.
 ! -->
<adm:managed-object name="je-index" plural-name="je-indexes"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The <adm:user-friendly-plural-name/> are used to store information
    that makes it possible to locate entries very quickly when processing
    search operations.
  </adm:synopsis>
  <adm:description>
    Indexing is performed on a per-attribute level and different types
    of indexing may be performed for different kinds of attributes based
    on how they are expected to be accessed during search operations.
  </adm:description>
  <adm:tag name="database" />
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.8</ldap:oid>
      <ldap:name>ds-cfg-je-index</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="index-attribute"
                mandatory="true"
                multi-valued="false"
                read-only="true">
    <adm:synopsis>
      This specifies the name of the attribute for which the index is
      to be maintained.
    </adm:synopsis>
    <adm:syntax>
      <adm:attribute-type/>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.40</ldap:oid>
        <ldap:name>ds-cfg-index-attribute</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="index-entry-limit"
                mandatory="false"
                multi-valued="false">
    <adm:synopsis>
      This specifies the maximum number of entries that will be allowed
      to match a given index key before that particular index key is no
      longer maintained.
    </adm:synopsis>
    <adm:description>
      This is analogous to the ALL IDs threshold in the Sun Java System
      Directory Server. If this is specified, it will override the JE
      backend-wide configuration. If any index keys have already
      reached this limit, indexes will need to be rebuilt before they
      will be allowed to use the new limit. For no limit, use 0 for the
      value.
    </adm:description>
    <adm:requires-admin-action>
      <adm:other>
        <adm:synopsis>
          If any index keys have already reached this limit, indexes
          will need to be rebuilt before they will be allowed to use
          the new limit.
        </adm:synopsis>
      </adm:other>
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>
          The index entry limit specified in the JE backend-wide
          configuration will be used.
        </adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="0" upper-limit="2147483647">
        <adm:unit-synopsis>
          Number of entries
        </adm:unit-synopsis>
      </adm:integer>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.41</ldap:oid>
        <ldap:name>ds-cfg-index-entry-limit</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="index-type"
                mandatory="true"
                multi-valued="true">
    <adm:synopsis>
      This specifies the type(s) of indexing that should be performed for
      the associated attribute.
    </adm:synopsis>
    <adm:description>
      For equality, presence, and substring index types, the associated
      attribute type must have a corrsponding matching rule.
    </adm:description>
    <adm:requires-admin-action>
      <adm:other>
        <adm:synopsis>
          If any new index types are added for an attribute and there
          are values for that attribute which already exist in the
          database, then the index will need to be rebuilt before it
          will be accurate.
        </adm:synopsis>
      </adm:other>
    </adm:requires-admin-action>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="equality">
          <adm:synopsis>
            This index type will be used to help improve the efficiency of
            searches using equality search filters.
          </adm:synopsis>
        </adm:value>
        <adm:value name="ordering">
          <adm:synopsis>
            This index type will be used to help improve the efficiency of
            searches using "greater than or equal to" or "less then or equal
            to" search filters.
          </adm:synopsis>
        </adm:value>
        <adm:value name="presence">
          <adm:synopsis>
            This index type will be used to help improve the efficiency of
            searches using the presence search filters.
          </adm:synopsis>
        </adm:value>
        <adm:value name="substring">
          <adm:synopsis>
            This index type will be used to help improve the efficiency of
            searches using substring search filters.
          </adm:synopsis>
        </adm:value>
        <adm:value name="approximate">
          <adm:synopsis>
            This index type will be used to help improve the efficiency of
            searches using approximate matching search filters.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.42</ldap:oid>
        <ldap:name>ds-cfg-index-type</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="index-substring-length"
                mandatory="false"
                multi-valued="false">
    <adm:synopsis>
      The length of substrings in a substring index.
    </adm:synopsis>
    <adm:requires-admin-action>
      <adm:other>
        <adm:synopsis>
          The index will need to be rebuilt before it will reflect
          the new value.
        </adm:synopsis>
      </adm:other>
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>6</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="3" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.380</ldap:oid>
        <ldap:name>ds-cfg-ds-cfg-index-substring-length</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/build-tools/org/opends/build/tools/CoverageDiff.java
@@ -171,7 +171,7 @@
    }
    catch(IOException ie)
    {
      System.out.println("WARNING: An error occured while loading EMMA " +
      System.out.println("WARNING: An error occurred while loading EMMA " +
          "data. Report will not contain any coverage information.");
    }
@@ -181,7 +181,7 @@
    }
    catch(IOException ie)
    {
      System.out.println("ERROR: An error occured while processing diff output: " + ie.toString() + " Quitting...");
      System.out.println("ERROR: An error occurred while processing diff output: " + ie.toString() + " Quitting...");
      return;
    }
    System.out.println("Coverage diff completed in " + (System.currentTimeMillis() - start) + " ms.");
@@ -512,7 +512,7 @@
    if(workingCopyFlag == null || otherCopyFlag == null)
    {
      throw new IOException("Error occured while parsing diff output");
      throw new IOException("Error occurred while parsing diff output");
    }
    else
    {
opends/src/quicksetup/org/opends/quicksetup/Installation.java
@@ -609,7 +609,7 @@
   * for backing up an installation during an upgrade.
   * @return File representing a new backup directory.  The directory
   * can be assumed to exist if this method returns cleanly.
   * @throws IOException if an error occured creating the directory.
   * @throws IOException if an error occurred creating the directory.
   */
  public File createHistoryBackupDirectory() throws IOException {
    File backupDirectory =
opends/src/server/org/opends/server/api/DebugLogPublisher.java
@@ -206,7 +206,7 @@
  /**
   * Determine whether a trace setting is alreadly defined for a
   * Determine whether a trace setting is already defined for a
   * particular scope.
   *
   * @param  scope  The scope for which to make the determination.
@@ -510,7 +510,7 @@
   * @param  key             The key to dump.
   * @param  data            The data to dump.
   * @param  stackTrace      The stack trace at the time the access
   *                         occured or null if its not available.
   *                         occurred or null if its not available.
   */
  public abstract void traceJEAccess(LogLevel level,
                                     TraceSettings settings,
opends/src/server/org/opends/server/backends/jeb/ApproximateIndexer.java
@@ -58,22 +58,21 @@
  private ApproximateMatchingRule approximateRule;
    /**
   * The attribute index configuration for which this instance will
   * The attribute type for which this instance will
   * generate index keys.
   */
  private IndexConfig indexConfig;
  private AttributeType attributeType;
  /**
   * Create a new attribute approximate indexer for the given index
   * configuration.
   * @param indexConfig The index configuration for which an indexer is
   * @param attributeType The attribute type for which an indexer is
   * required.
   */
  public ApproximateIndexer(IndexConfig indexConfig)
  public ApproximateIndexer(AttributeType attributeType)
  {
    this.indexConfig = indexConfig;
    approximateRule =
        indexConfig.getAttributeType().getApproximateMatchingRule();
    this.attributeType = attributeType;
    this.approximateRule = attributeType.getApproximateMatchingRule();
  }
  /**
@@ -83,7 +82,7 @@
   */
  public String toString()
  {
    return indexConfig.getAttributeType().getNameOrOID() + ".approximate";
    return attributeType.getNameOrOID() + ".approximate";
  }
@@ -110,7 +109,7 @@
                       Set<ASN1OctetString> keys)
  {
    List<Attribute> attrList =
         entry.getAttribute(indexConfig.getAttributeType());
         entry.getAttribute(attributeType);
    if (attrList != null)
    {
      indexAttribute(attrList, keys);
@@ -135,11 +134,11 @@
  {
    List<Attribute> attrList;
    attrList = oldEntry.getAttribute(indexConfig.getAttributeType());
    attrList = oldEntry.getAttribute(attributeType);
    Set<ASN1OctetString> oldSet = new HashSet<ASN1OctetString>();
    indexAttribute(attrList, oldSet);
    attrList = newEntry.getAttribute(indexConfig.getAttributeType());
    attrList = newEntry.getAttribute(attributeType);
    Set<ASN1OctetString> newSet = new HashSet<ASN1OctetString>();
    indexAttribute(attrList, newSet);
opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java
@@ -32,27 +32,24 @@
import java.util.List;
import java.util.Set;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.*;
import org.opends.server.api.SubstringMatchingRule;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.api.ApproximateMatchingRule;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.SearchFilter;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.*;
import org.opends.server.admin.std.server.JEIndexCfg;
import org.opends.server.admin.std.meta.JEIndexCfgDefn;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.config.ConfigException;
import static org.opends.server.messages.JebMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import org.opends.server.core.DirectoryServer;
import org.opends.server.util.StaticUtils;
/**
 * Class representing an attribute index.
@@ -67,6 +64,7 @@
 * then we would not need a separate ordering index.
 */
public class AttributeIndex
    implements ConfigurationChangeListener<JEIndexCfg>
{
  /**
   * The tracer object for the debug logger.
@@ -84,12 +82,14 @@
  /**
   * The entryContainer in which this attribute index resides.
   */
  EntryContainer entryContainer;
  private EntryContainer entryContainer;
  private Environment env;
  /**
   * The attribute index configuration.
   */
  IndexConfig indexConfig;
  private JEIndexCfg indexConfig;
  /**
   * The index database for attribute equality.
@@ -116,107 +116,204 @@
   */
  Index approximateIndex = null;
  private State state;
  private int cursorEntryLimit = 100000;
  private int backendIndexEntryLimit = 4000;
  /**
   * Create a new attribute index object.
   * @param entryContainer The entryContainer of this attribute index.
   * @param state The state database to persist index state info.
   * @param env The JE environment handle.
   * @param indexConfig The attribute index configuration.
   * @param backendIndexEntryLimit The backend index entry limit to use
   *        if none is specified for this attribute index.
   * @throws DatabaseException if a JE database error occurs.
   * @throws ConfigException if a configuration related error occurs.
   */
  public AttributeIndex(EntryContainer entryContainer, IndexConfig indexConfig)
  public AttributeIndex(JEIndexCfg indexConfig, State state,
                        int backendIndexEntryLimit,
                        Environment env,
                        EntryContainer entryContainer)
      throws DatabaseException, ConfigException
  {
    this.entryContainer = entryContainer;
    this.env = env;
    this.indexConfig = indexConfig;
    this.backendIndexEntryLimit = backendIndexEntryLimit;
    this.state = state;
    AttributeType attrType = indexConfig.getAttributeType();
    AttributeType attrType = indexConfig.getIndexAttribute();
    String name = attrType.getNameOrOID();
    int indexEntryLimit = backendIndexEntryLimit;
    if (indexConfig.isEqualityIndex())
    if(indexConfig.getIndexEntryLimit() != null)
    {
      Indexer equalityIndexer = new EqualityIndexer(indexConfig);
      this.equalityIndex = new Index(this.entryContainer, name + ".equality",
      indexEntryLimit = indexConfig.getIndexEntryLimit();
    }
    if (indexConfig.getIndexType().contains(JEIndexCfgDefn.IndexType.EQUALITY))
    {
      if (attrType.getEqualityMatchingRule() == null)
      {
        int messageID = MSGID_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE;
        String message = getMessage(messageID, attrType, "equality");
        throw new ConfigException(messageID, message);
      }
      Indexer equalityIndexer = new EqualityIndexer(attrType);
      this.equalityIndex = new Index(name + ".equality",
                                     equalityIndexer,
                                     indexConfig.getEqualityEntryLimit(),
                                     indexConfig.getCursorEntryLimit());
                                     state,
                                     indexEntryLimit,
                                     cursorEntryLimit,
                                     env,
                                     entryContainer);
    }
    if (indexConfig.isPresenceIndex())
    if (indexConfig.getIndexType().contains(JEIndexCfgDefn.IndexType.PRESENCE))
    {
      Indexer presenceIndexer = new PresenceIndexer(indexConfig);
      this.presenceIndex = new Index(this.entryContainer, name + ".presence",
      Indexer presenceIndexer = new PresenceIndexer(attrType);
      this.presenceIndex = new Index(name + ".presence",
                                     presenceIndexer,
                                     indexConfig.getPresenceEntryLimit(),
                                     indexConfig.getCursorEntryLimit());
                                     state,
                                     indexEntryLimit,
                                     cursorEntryLimit,
                                     env,
                                     entryContainer);
    }
    if (indexConfig.isSubstringIndex())
    if (indexConfig.getIndexType().contains(JEIndexCfgDefn.IndexType.SUBSTRING))
    {
      Indexer substringIndexer = new SubstringIndexer(indexConfig);
      this.substringIndex = new Index(this.entryContainer, name + ".substring",
      if (attrType.getSubstringMatchingRule() == null)
      {
        int messageID = MSGID_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE;
        String message = getMessage(messageID, attrType, "substring");
        throw new ConfigException(messageID, message);
      }
      Indexer substringIndexer = new SubstringIndexer(attrType,
                                         indexConfig.getIndexSubstringLength());
      this.substringIndex = new Index(name + ".substring",
                                     substringIndexer,
                                     indexConfig.getSubstringEntryLimit(),
                                     indexConfig.getCursorEntryLimit());
                                     state,
                                     indexEntryLimit,
                                     cursorEntryLimit,
                                     env,
                                     entryContainer);
    }
    if (indexConfig.isOrderingIndex())
    if (indexConfig.getIndexType().contains(JEIndexCfgDefn.IndexType.ORDERING))
    {
      Indexer orderingIndexer = new OrderingIndexer(indexConfig);
      this.orderingIndex = new Index(this.entryContainer, name + ".ordering",
      if (attrType.getOrderingMatchingRule() == null)
      {
        int messageID = MSGID_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE;
        String message = getMessage(messageID, attrType, "ordering");
        throw new ConfigException(messageID, message);
      }
      Indexer orderingIndexer = new OrderingIndexer(attrType);
      this.orderingIndex = new Index(name + ".ordering",
                                     orderingIndexer,
                                     indexConfig.getEqualityEntryLimit(),
                                     indexConfig.getCursorEntryLimit());
                                     state,
                                     indexEntryLimit,
                                     cursorEntryLimit,
                                     env,
                                     entryContainer);
    }
    if (indexConfig.isApproximateIndex())
    if (indexConfig.getIndexType().contains(
        JEIndexCfgDefn.IndexType.APPROXIMATE))
    {
      Indexer approximateIndexer = new ApproximateIndexer(indexConfig);
      this.approximateIndex = new Index(this.entryContainer,
                                        name + ".approximate",
      if (attrType.getApproximateMatchingRule() == null)
      {
        int messageID = MSGID_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE;
        String message = getMessage(messageID, attrType, "approximate");
        throw new ConfigException(messageID, message);
      }
      Indexer approximateIndexer = new ApproximateIndexer(attrType);
      this.approximateIndex = new Index(name + ".approximate",
                                        approximateIndexer,
                                        indexConfig.getEqualityEntryLimit(),
                                        indexConfig.getCursorEntryLimit());
                                        state,
                                        indexEntryLimit,
                                        cursorEntryLimit,
                                        env,
                                        entryContainer);
    }
    this.indexConfig.addChangeListener(this);
  }
  /**
   * Open the attribute index.
   *
   * @param dbConfig The JE Database Config that will be used to open
   *                 underlying JE databases.
   *
   * @throws DatabaseException If an error occurs opening the underlying
   *                           databases.
   * @throws DatabaseException if a JE database error occurs while
   * openning the index.
   */
  public void open(DatabaseConfig dbConfig) throws DatabaseException
  public void open() throws DatabaseException
  {
    if (equalityIndex != null)
    {
      equalityIndex.open(dbConfig);
      equalityIndex.open();
    }
    if (presenceIndex != null)
    {
      presenceIndex.open(dbConfig);
      presenceIndex.open();
    }
    if (substringIndex != null)
    {
      substringIndex.open(dbConfig);
      substringIndex.open();
    }
    if (orderingIndex != null)
    {
      orderingIndex.open(dbConfig);
      orderingIndex.open();
    }
    if (approximateIndex != null)
    {
      approximateIndex.open(dbConfig);
      approximateIndex.open();
    }
  }
  /**
   * Close the attribute index.
   *
   * @throws DatabaseException if a JE database error occurs while
   * openning the index.
   */
  public void close()
  public void close() throws DatabaseException
  {
    if (equalityIndex != null)
    {
      equalityIndex.close();
    }
    if (presenceIndex != null)
    {
      presenceIndex.close();
    }
    if (substringIndex != null)
    {
      substringIndex.close();
    }
    if (orderingIndex != null)
    {
      orderingIndex.close();
    }
    if (approximateIndex != null)
    {
      approximateIndex.close();
    }
    indexConfig.removeChangeListener(this);
    // The entryContainer is responsible for closing the JE databases.
  }
@@ -226,10 +323,18 @@
   */
  public AttributeType getAttributeType()
  {
    return indexConfig.getAttributeType();
    return indexConfig.getIndexAttribute();
  }
  //TODO: Make all modify/add methods return success boolean
  /**
   * Get the JE index configuration used by this index.
   * @return The configuration in effect.
   */
  public JEIndexCfg getConfiguration()
  {
    return indexConfig;
  }
  /**
   * Update the attribute index for a new entry.
   *
@@ -237,7 +342,7 @@
   * @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 alreadly exists for some keys.
   *         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.
@@ -406,7 +511,7 @@
    // concurrent writers.
    Set<ByteString> set = new HashSet<ByteString>();
    int substrLength = indexConfig.getSubstringLength();
    int substrLength = indexConfig.getIndexSubstringLength();
    byte[] keyBytes;
    // Example: The value is ABCDE and the substring length is 3.
@@ -432,7 +537,7 @@
   */
  private EntryIDSet matchSubstring(byte[] bytes)
  {
    int substrLength = indexConfig.getSubstringLength();
    int substrLength = indexConfig.getIndexSubstringLength();
    // There are two cases, depending on whether the user-provided
    // substring is smaller than the configured index substring length or not.
@@ -555,7 +660,7 @@
   */
  public EntryIDSet evaluateEqualityFilter(SearchFilter equalityFilter)
  {
    if (!indexConfig.isEqualityIndex())
    if (equalityIndex == null)
    {
      return new EntryIDSet();
    }
@@ -589,7 +694,7 @@
   */
  public EntryIDSet evaluatePresenceFilter(SearchFilter filter)
  {
    if (!indexConfig.isPresenceIndex())
    if (presenceIndex == null)
    {
      return new EntryIDSet();
    }
@@ -607,7 +712,7 @@
   */
  public EntryIDSet evaluateGreaterOrEqualFilter(SearchFilter filter)
  {
    if (!indexConfig.isOrderingIndex() || orderingIndex == null)
    if (orderingIndex == null)
    {
      return new EntryIDSet();
    }
@@ -647,7 +752,7 @@
   */
  public EntryIDSet evaluateLessOrEqualFilter(SearchFilter filter)
  {
    if (!indexConfig.isOrderingIndex() || orderingIndex == null)
    if (orderingIndex == null)
    {
      return new EntryIDSet();
    }
@@ -698,7 +803,7 @@
      if (filter.getSubInitialElement() != null)
      {
        // Use the equality index for initial substrings if possible.
        if (indexConfig.isEqualityIndex())
        if (equalityIndex != null)
        {
          ByteString normValue =
               matchRule.normalizeSubstring(filter.getSubInitialElement());
@@ -719,7 +824,7 @@
        }
      }
      if (!indexConfig.isSubstringIndex())
      if (substringIndex == null)
      {
        return results;
      }
@@ -864,7 +969,7 @@
   */
  public EntryIDSet evaluateApproximateFilter(SearchFilter approximateFilter)
  {
    if (!indexConfig.isApproximateIndex())
    if (approximateIndex == null)
    {
      return new EntryIDSet();
    }
@@ -892,37 +997,6 @@
    }
  }
  /**
   * Remove the index from disk. The index must not be open.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void removeIndex() throws DatabaseException
  {
    AttributeType attrType = indexConfig.getAttributeType();
    String name = attrType.getNameOrOID();
    if (indexConfig.isEqualityIndex())
    {
      entryContainer.removeDatabase(name + ".equality");
    }
    if (indexConfig.isPresenceIndex())
    {
      entryContainer.removeDatabase(name + ".presence");
    }
    if (indexConfig.isSubstringIndex())
    {
      entryContainer.removeDatabase(name + ".substring");
    }
    if (indexConfig.isOrderingIndex())
    {
      entryContainer.removeDatabase(name + ".ordering");
    }
    if (indexConfig.isApproximateIndex())
    {
      entryContainer.removeDatabase(name + ".approximate");
    }
  }
  /**
   * Return the number of values that have exceeded the entry limit since this
   * object was created.
@@ -963,50 +1037,553 @@
  }
  /**
   * Removes all records related to this attribute index.
   * @param txn A JE database transaction to be used during the clear operation
   *            or null if not required. Using transactions increases the chance
   *            of lock contention.
   * @return The number of records removed.
   * @throws DatabaseException If an error occurs while cleaning the database.
   * Get a list of the databases opened by this attribute index.
   * @param dbList A list of database containers.
   */
  public long clear(Transaction txn) throws DatabaseException
  public void listDatabases(List<DatabaseContainer> dbList)
  {
    long deletedCount = 0;
    if (equalityIndex != null)
    {
      deletedCount += equalityIndex.clear(txn);
      dbList.add(equalityIndex);
    }
    if (presenceIndex != null)
    {
      deletedCount += presenceIndex.clear(txn);
      dbList.add(presenceIndex);
    }
    if (substringIndex != null)
    {
      deletedCount += substringIndex.clear(txn);
      dbList.add(substringIndex);
    }
    if (orderingIndex != null)
    {
      deletedCount += orderingIndex.clear(txn);
      dbList.add(orderingIndex);
    }
    if (approximateIndex != null)
    {
      deletedCount += approximateIndex.clear(txn);
      dbList.add(approximateIndex);
    }
    return deletedCount;
  }
  /**
   * Get a string representation of this object.
   * @return return A string representation of this object.
   */
  public String toString()
  {
    return indexConfig.getAttributeType().getNameOrOID();
    return getName();
  }
  /**
   * Set the index entry limit used by the backend using this attribute index.
   * This index will use the backend entry limit only if there is not one
   * specified for this index.
   *
   * @param backendIndexEntryLimit The backend index entry limit.
   * @return True if a rebuild is required or false otherwise.
   */
  public synchronized boolean setBackendIndexEntryLimit(
      int backendIndexEntryLimit)
  {
    // Only update if there is no limit specified for this index.
    boolean rebuildRequired = false;
    if(indexConfig.getIndexEntryLimit() == null)
    {
      if(equalityIndex != null)
      {
        rebuildRequired |=
            equalityIndex.setIndexEntryLimit(backendIndexEntryLimit);
      }
      if(presenceIndex != null)
      {
        rebuildRequired |=
            presenceIndex.setIndexEntryLimit(backendIndexEntryLimit);
      }
      if(substringIndex != null)
      {
        rebuildRequired |=
            substringIndex.setIndexEntryLimit(backendIndexEntryLimit);
      }
      if(orderingIndex != null)
      {
        rebuildRequired |=
            orderingIndex.setIndexEntryLimit(backendIndexEntryLimit);
      }
      if(approximateIndex != null)
      {
        rebuildRequired |=
            approximateIndex.setIndexEntryLimit(backendIndexEntryLimit);
      }
    }
    this.backendIndexEntryLimit = backendIndexEntryLimit;
    return rebuildRequired;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized boolean isConfigurationChangeAcceptable(
      JEIndexCfg cfg,
      List<String> unacceptableReasons)
  {
    AttributeType attrType = cfg.getIndexAttribute();
    if (cfg.getIndexType().contains(JEIndexCfgDefn.IndexType.EQUALITY))
    {
      if (equalityIndex == null && attrType.getEqualityMatchingRule() == null)
      {
        int messageID = MSGID_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE;
        String message = getMessage(messageID, attrType, "equality");
        unacceptableReasons.add(message);
        return false;
      }
    }
    if (cfg.getIndexType().contains(JEIndexCfgDefn.IndexType.SUBSTRING))
    {
      if (substringIndex == null && attrType.getSubstringMatchingRule() == null)
      {
        int messageID = MSGID_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE;
        String message = getMessage(messageID, attrType, "substring");
        unacceptableReasons.add(message);
        return false;
      }
    }
    if (cfg.getIndexType().contains(JEIndexCfgDefn.IndexType.ORDERING))
    {
      if (orderingIndex == null && attrType.getOrderingMatchingRule() == null)
      {
        int messageID = MSGID_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE;
        String message = getMessage(messageID, attrType, "ordering");
        unacceptableReasons.add(message);
        return false;
      }
    }
    if (cfg.getIndexType().contains(JEIndexCfgDefn.IndexType.APPROXIMATE))
    {
      if (approximateIndex == null &&
          attrType.getApproximateMatchingRule() == null)
      {
        int messageID = MSGID_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE;
        String message = getMessage(messageID, attrType, "approximate");
        unacceptableReasons.add(message);
        return false;
      }
    }
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized ConfigChangeResult applyConfigurationChange(
      JEIndexCfg cfg)
  {
    ConfigChangeResult ccr;
    boolean adminActionRequired = false;
    ArrayList<String> messages = new ArrayList<String>();
    try
    {
      AttributeType attrType = cfg.getIndexAttribute();
      String name = attrType.getNameOrOID();
      int indexEntryLimit = backendIndexEntryLimit;
      if(cfg.getIndexEntryLimit() != null)
      {
        indexEntryLimit = cfg.getIndexEntryLimit();
      }
      if (cfg.getIndexType().contains(JEIndexCfgDefn.IndexType.EQUALITY))
      {
        if (equalityIndex == null)
        {
          // Adding equality index
          Indexer equalityIndexer = new EqualityIndexer(attrType);
          equalityIndex = new Index(name + ".equality",
                                    equalityIndexer,
                                    state,
                                    indexEntryLimit,
                                    cursorEntryLimit,
                                    env,
                                    entryContainer);
          equalityIndex.open();
          adminActionRequired = true;
          int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
          messages.add(getMessage(msgID, name + ".equality"));
        }
        else
        {
          // already exists. Just update index entry limit.
          if(this.equalityIndex.setIndexEntryLimit(indexEntryLimit))
          {
            adminActionRequired = true;
            int msgID = MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD;
            String message = getMessage(msgID, name + ".equality");
            messages.add(message);
            this.equalityIndex.setIndexEntryLimit(indexEntryLimit);
          }
        }
      }
      else
      {
        if (equalityIndex != null)
        {
          entryContainer.exclusiveLock.lock();
          try
          {
            entryContainer.removeDatabase(equalityIndex);
            equalityIndex = null;
          }
          catch(DatabaseException de)
          {
            messages.add(StaticUtils.stackTraceToSingleLineString(de));
            ccr = new ConfigChangeResult(
                DirectoryServer.getServerErrorResultCode(), false, messages);
            return ccr;
          }
          finally
          {
            entryContainer.exclusiveLock.unlock();
          }
        }
      }
      if (cfg.getIndexType().contains(JEIndexCfgDefn.IndexType.PRESENCE))
      {
        if(presenceIndex == null)
        {
          Indexer presenceIndexer = new PresenceIndexer(attrType);
          presenceIndex = new Index(name + ".presence",
                                    presenceIndexer,
                                    state,
                                    indexEntryLimit,
                                    cursorEntryLimit,
                                    env,
                                    entryContainer);
          presenceIndex.open();
          adminActionRequired = true;
          int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
          messages.add(getMessage(msgID, name + ".presence"));
        }
        else
        {
          // already exists. Just update index entry limit.
          if(this.presenceIndex.setIndexEntryLimit(indexEntryLimit))
          {
            adminActionRequired = true;
            int msgID = MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD;
            String message = getMessage(msgID, name + ".presence");
            messages.add(message);
          }
        }
      }
      else
      {
        if (presenceIndex != null)
        {
          entryContainer.exclusiveLock.lock();
          try
          {
            entryContainer.removeDatabase(presenceIndex);
            presenceIndex = null;
          }
          catch(DatabaseException de)
          {
            messages.add(StaticUtils.stackTraceToSingleLineString(de));
            ccr = new ConfigChangeResult(
                DirectoryServer.getServerErrorResultCode(), false, messages);
            return ccr;
          }
          finally
          {
            entryContainer.exclusiveLock.unlock();
          }
        }
      }
      if (cfg.getIndexType().contains(JEIndexCfgDefn.IndexType.SUBSTRING))
      {
        if(substringIndex == null)
        {
          Indexer substringIndexer = new SubstringIndexer(
              attrType, cfg.getIndexSubstringLength());
          substringIndex = new Index(name + ".substring",
                                     substringIndexer,
                                     state,
                                     indexEntryLimit,
                                     cursorEntryLimit,
                                     env,
                                     entryContainer);
          substringIndex.open();
          adminActionRequired = true;
          int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
          messages.add(getMessage(msgID, name + ".substring"));
        }
        else
        {
          // already exists. Just update index entry limit.
          if(this.substringIndex.setIndexEntryLimit(indexEntryLimit))
          {
            adminActionRequired = true;
            int msgID = MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD;
            String message = getMessage(msgID, name + ".substring");
            messages.add(message);
          }
          if(indexConfig.getIndexSubstringLength() !=
              cfg.getIndexSubstringLength())
          {
            Indexer substringIndexer = new SubstringIndexer(
                attrType, cfg.getIndexSubstringLength());
            this.substringIndex.setIndexer(substringIndexer);
          }
        }
      }
      else
      {
        if (substringIndex != null)
        {
          entryContainer.exclusiveLock.lock();
          try
          {
            entryContainer.removeDatabase(substringIndex);
            substringIndex = null;
          }
          catch(DatabaseException de)
          {
            messages.add(StaticUtils.stackTraceToSingleLineString(de));
            ccr = new ConfigChangeResult(
                DirectoryServer.getServerErrorResultCode(), false, messages);
            return ccr;
          }
          finally
          {
            entryContainer.exclusiveLock.unlock();
          }
        }
      }
      if (cfg.getIndexType().contains(JEIndexCfgDefn.IndexType.ORDERING))
      {
        if(orderingIndex == null)
        {
          Indexer orderingIndexer = new OrderingIndexer(attrType);
          orderingIndex = new Index(name + ".ordering",
                                    orderingIndexer,
                                    state,
                                    indexEntryLimit,
                                    cursorEntryLimit,
                                    env,
                                    entryContainer);
          orderingIndex.open();
          adminActionRequired = true;
          int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
          messages.add(getMessage(msgID, name + ".ordering"));
        }
        else
        {
          // already exists. Just update index entry limit.
          if(this.orderingIndex.setIndexEntryLimit(indexEntryLimit))
          {
            adminActionRequired = true;
            int msgID = MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD;
            String message = getMessage(msgID, name + ".ordering");
            messages.add(message);
          }
        }
      }
      else
      {
        if (orderingIndex != null)
        {
          entryContainer.exclusiveLock.lock();
          try
          {
            entryContainer.removeDatabase(orderingIndex);
            orderingIndex = null;
          }
          catch(DatabaseException de)
          {
            messages.add(StaticUtils.stackTraceToSingleLineString(de));
            ccr = new ConfigChangeResult(
                DirectoryServer.getServerErrorResultCode(), false, messages);
            return ccr;
          }
          finally
          {
            entryContainer.exclusiveLock.unlock();
          }
        }
      }
      if (cfg.getIndexType().contains(JEIndexCfgDefn.IndexType.APPROXIMATE))
      {
        if(approximateIndex == null)
        {
          Indexer approximateIndexer = new ApproximateIndexer(attrType);
          approximateIndex = new Index(name + ".approximate",
                                       approximateIndexer,
                                       state,
                                       indexEntryLimit,
                                       cursorEntryLimit,
                                       env,
                                       entryContainer);
          approximateIndex.open();
          adminActionRequired = true;
          int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
          messages.add(getMessage(msgID, name + ".approximate"));
        }
        else
        {
          // already exists. Just update index entry limit.
          if(this.approximateIndex.setIndexEntryLimit(indexEntryLimit))
          {
            adminActionRequired = true;
            int msgID = MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD;
            String message = getMessage(msgID, name + ".approximate");
            messages.add(message);
          }
        }
      }
      else
      {
        if (approximateIndex != null)
        {
          entryContainer.exclusiveLock.lock();
          try
          {
            entryContainer.removeDatabase(approximateIndex);
            approximateIndex = null;
          }
          catch(DatabaseException de)
          {
            messages.add(StaticUtils.stackTraceToSingleLineString(de));
            ccr = new ConfigChangeResult(
                DirectoryServer.getServerErrorResultCode(), false, messages);
            return ccr;
          }
          finally
          {
            entryContainer.exclusiveLock.unlock();
          }
        }
      }
      indexConfig = cfg;
      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
                                    messages);
    }
    catch(Exception e)
    {
      messages.add(StaticUtils.stackTraceToSingleLineString(e));
      ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
                                   adminActionRequired,
                                   messages);
      return ccr;
    }
  }
  /**
   * Set the index trust state.
   * @param txn A database transaction, or null if none is required.
   * @param trusted True if this index should be trusted or false
   *                otherwise.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public synchronized void setTrusted(Transaction txn, boolean trusted)
      throws DatabaseException
  {
    if (equalityIndex != null)
    {
      equalityIndex.setTrusted(txn, trusted);
    }
    if (presenceIndex != null)
    {
      presenceIndex.setTrusted(txn, trusted);
    }
    if (substringIndex != null)
    {
      substringIndex.setTrusted(txn, trusted);
    }
    if (orderingIndex != null)
    {
      orderingIndex.setTrusted(txn, trusted);
    }
    if (approximateIndex != null)
    {
      approximateIndex.setTrusted(txn, trusted);
    }
  }
  /**
   * Set the rebuild status of this index.
   * @param rebuildRunning True if a rebuild process on this index
   *                       is running or False otherwise.
   */
  public synchronized void setRebuildStatus(boolean rebuildRunning)
  {
    if (equalityIndex != null)
    {
      equalityIndex.setRebuildStatus(rebuildRunning);
    }
    if (presenceIndex != null)
    {
      presenceIndex.setRebuildStatus(rebuildRunning);
    }
    if (substringIndex != null)
    {
      substringIndex.setRebuildStatus(rebuildRunning);
    }
    if (orderingIndex != null)
    {
      orderingIndex.setRebuildStatus(rebuildRunning);
    }
    if (approximateIndex != null)
    {
      approximateIndex.setRebuildStatus(rebuildRunning);
    }
  }
  /**
   * Get the JE database name prefix for indexes in this attribute
   * index.
   *
   * @return JE database name for this database container.
   */
  public String getName()
  {
    StringBuilder builder = new StringBuilder();
    builder.append(entryContainer.getContainerName());
    builder.append("_");
    builder.append(indexConfig.getIndexAttribute().getNameOrOID());
    return builder.toString();
  }
}
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -27,12 +27,13 @@
package org.opends.server.backends.jeb;
import java.io.IOException;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.util.Arrays;
@@ -41,6 +42,7 @@
import java.util.zip.CheckedInputStream;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentConfig;
import org.opends.server.api.Backend;
import org.opends.server.api.MonitorProvider;
@@ -53,6 +55,8 @@
import org.opends.server.core.SearchOperation;
import org.opends.server.util.LDIFException;
import org.opends.server.util.Validator;
import org.opends.server.util.StaticUtils;
import static org.opends.server.util.StaticUtils.getFileForPath;
import static org.opends.server.messages.BackendMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
@@ -71,8 +75,8 @@
 * locally in a Sleepycat JE database.
 */
public class BackendImpl
     extends Backend
     implements ConfigurationChangeListener<JEBackendCfg>
    extends Backend
    implements ConfigurationChangeListener<JEBackendCfg>
{
  /**
   * The tracer object for the debug logger.
@@ -83,7 +87,6 @@
  /**
   * The configuration of this JE backend.
   */
  private Config config;
  private JEBackendCfg cfg;
  /**
@@ -105,7 +108,7 @@
   * A list of monitor providers created for this backend instance.
   */
  private ArrayList<MonitorProvider> monitorProviders =
       new ArrayList<MonitorProvider>();
      new ArrayList<MonitorProvider>();
  /**
   * The controls supported by this backend.
@@ -193,20 +196,25 @@
    }
  }
 /**
  * This method will attempt to checksum the current JE db environment by
  * computing the Adler-32 checksum on the latest JE log file available.
  *
  * @return  The checksum of JE db environment or zero if checksum failed.
  */
  /**
   * This method will attempt to checksum the current JE db environment by
   * computing the Adler-32 checksum on the latest JE log file available.
   *
   * @return  The checksum of JE db environment or zero if checksum failed.
   */
  private long checksumDbEnv() {
    List<File> jdbFiles =
     Arrays.asList(config.getBackendDirectory().listFiles(new FilenameFilter() {
      public boolean accept(File dir, String name) {
        return name.endsWith(".jdb");
      }
    }));
    File backendDirectory = getFileForPath(cfg.getBackendDirectory());
    List<File> jdbFiles = new ArrayList<File>();
    if(backendDirectory.isDirectory())
    {
      jdbFiles =
          Arrays.asList(backendDirectory.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
              return name.endsWith(".jdb");
            }
          }));
    }
    if ( !jdbFiles.isEmpty() ) {
      Collections.sort(jdbFiles, Collections.reverseOrder());
@@ -274,17 +282,12 @@
   * {@inheritDoc}
   */
  public void configureBackend(Configuration cfg)
       throws ConfigException
      throws ConfigException
  {
    Validator.ensureNotNull(cfg);
    Validator.ensureTrue(cfg instanceof JEBackendCfg);
    // Initialize a config object
    Config config = new Config();
    config.initializeConfig((JEBackendCfg)cfg);
    this.cfg = (JEBackendCfg)cfg;
    this.config = config;
  }
@@ -293,46 +296,22 @@
   * {@inheritDoc}
   */
  public void initializeBackend()
       throws ConfigException, InitializationException
      throws ConfigException, InitializationException
  {
    // Checksum this db environment and register its offline state id/checksum.
    DirectoryServer.registerOfflineBackendStateID(this.getBackendID(),
      checksumDbEnv());
                                                  checksumDbEnv());
    // Open the database environment
    try {
      rootContainer = new RootContainer(config, this);
      rootContainer.open();
    }
    catch (DatabaseException e)
    if(rootContainer == null)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      String message = getMessage(MSGID_JEB_OPEN_ENV_FAIL,
                                  e.getMessage());
      throw new InitializationException(MSGID_JEB_OPEN_ENV_FAIL, message, e);
    }
      EnvironmentConfig envConfig =
          ConfigurableEnvironment.parseConfigEntry(cfg);
    try
    {
      rootContainer.openEntryContainers(config.getBaseDNs());
    }
    catch (DatabaseException databaseException)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, databaseException);
      }
      String message = getMessage(MSGID_JEB_OPEN_DATABASE_FAIL,
                                  databaseException.getMessage());
      throw new InitializationException(MSGID_JEB_OPEN_DATABASE_FAIL, message,
                                        databaseException);
      initializeRootContainer(envConfig);
    }
    // Preload the database cache.
    rootContainer.preload();
    rootContainer.preload(cfg.getBackendPreloadTimeLimit());
    try
    {
@@ -355,7 +334,7 @@
                                        message, databaseException);
    }
    for (DN dn : config.getBaseDNs())
    for (DN dn : cfg.getBackendBaseDN())
    {
      try
      {
@@ -383,8 +362,6 @@
    // Register this backend as a change listener.
    cfg.addJEChangeListener(this);
    cfg.addJEChangeListener(config);
    cfg.addJEChangeListener(rootContainer);
  }
  /**
@@ -399,8 +376,6 @@
  public void finalizeBackend()
  {
    // Deregister as a change listener.
    cfg.removeJEChangeListener(rootContainer);
    cfg.removeJEChangeListener(config);
    cfg.removeJEChangeListener(this);
    // Deregister our base DNs.
@@ -589,7 +564,9 @@
   */
  public DN[] getBaseDNs()
  {
    return config.getBaseDNs();
    Set<DN> dnSet = cfg.getBackendBaseDN();
    DN[] baseDNs = new DN[dnSet.size()];
    return dnSet.toArray(baseDNs);
  }
@@ -632,42 +609,41 @@
  public Entry getEntry(DN entryDN) throws DirectoryException
  {
    readerBegin();
    EntryContainer ec = rootContainer.getEntryContainer(entryDN);
    ec.sharedLock.lock();
    Entry entry;
    try
    {
      EntryContainer ec = rootContainer.getEntryContainer(entryDN);
      Entry entry;
      try
      entry = ec.getEntry(entryDN);
    }
    catch (DatabaseException e)
    {
      if (debugEnabled())
      {
        entry = ec.getEntry(entryDN);
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      catch (DatabaseException e)
      String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION,
                                  e.getMessage());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, MSGID_JEB_DATABASE_EXCEPTION);
    }
    catch (JebException e)
    {
      if (debugEnabled())
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION,
                                    e.getMessage());
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, MSGID_JEB_DATABASE_EXCEPTION);
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      catch (JebException e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     e.getMessage(),
                                     e.getMessageID());
      }
      return entry;
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessage(),
                                   e.getMessageID());
    }
    finally
    {
      ec.sharedLock.unlock();
      readerEnd();
    }
    return entry;
  }
@@ -685,42 +661,40 @@
   *                            entry.
   */
  public void addEntry(Entry entry, AddOperation addOperation)
       throws DirectoryException
      throws DirectoryException
  {
    writerBegin();
    DN entryDN = entry.getDN();
    EntryContainer ec = rootContainer.getEntryContainer(entryDN);
    ec.sharedLock.lock();
    try
    {
      DN entryDN = entry.getDN();
      EntryContainer ec = rootContainer.getEntryContainer(entryDN);
      try
      ec.addEntry(entry, addOperation);
    }
    catch (DatabaseException e)
    {
      if (debugEnabled())
      {
        ec.addEntry(entry, addOperation);
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      catch (DatabaseException e)
      String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION,
                                  e.getMessage());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, MSGID_JEB_DATABASE_EXCEPTION);
    }
    catch (JebException e)
    {
      if (debugEnabled())
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION,
                                    e.getMessage());
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, MSGID_JEB_DATABASE_EXCEPTION);
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      catch (JebException e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     e.getMessage(),
                                     e.getMessageID());
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessage(),
                                   e.getMessageID());
    }
    finally
    {
      ec.sharedLock.unlock();
      writerEnd();
    }
  }
@@ -741,40 +715,40 @@
   *                            entry.
   */
  public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
       throws DirectoryException
      throws DirectoryException
  {
    writerBegin();
    EntryContainer ec = rootContainer.getEntryContainer(entryDN);
    ec.sharedLock.lock();
    try
    {
      EntryContainer ec = rootContainer.getEntryContainer(entryDN);
      try
      ec.deleteEntry(entryDN, deleteOperation);
    }
    catch (DatabaseException e)
    {
      if (debugEnabled())
      {
        ec.deleteEntry(entryDN, deleteOperation);
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      catch (DatabaseException e)
      String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION,
                                  e.getMessage());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, MSGID_JEB_DATABASE_EXCEPTION);
    }
    catch (JebException e)
    {
      if (debugEnabled())
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION,
                                    e.getMessage());
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, MSGID_JEB_DATABASE_EXCEPTION);
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      catch (JebException e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     e.getMessage(),
                                     e.getMessageID());
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessage(),
                                   e.getMessageID());
    }
    finally
    {
      ec.sharedLock.unlock();
      writerEnd();
    }
  }
@@ -795,42 +769,42 @@
   *                            entry.
   */
  public void replaceEntry(Entry entry, ModifyOperation modifyOperation)
       throws DirectoryException
      throws DirectoryException
  {
    writerBegin();
    DN entryDN = entry.getDN();
    EntryContainer ec = rootContainer.getEntryContainer(entryDN);
    ec.sharedLock.lock();
    try
    {
      DN entryDN = entry.getDN();
      EntryContainer ec = rootContainer.getEntryContainer(entryDN);
      try
      ec.replaceEntry(entry, modifyOperation);
    }
    catch (DatabaseException e)
    {
      if (debugEnabled())
      {
        ec.replaceEntry(entry, modifyOperation);
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      catch (DatabaseException e)
      String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION,
                                  e.getMessage());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, MSGID_JEB_DATABASE_EXCEPTION);
    }
    catch (JebException e)
    {
      if (debugEnabled())
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION,
                                    e.getMessage());
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, MSGID_JEB_DATABASE_EXCEPTION);
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      catch (JebException e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     e.getMessage(),
                                     e.getMessageID());
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessage(),
                                   e.getMessageID());
    }
    finally
    {
      ec.sharedLock.unlock();
      writerEnd();
    }
  }
@@ -857,24 +831,25 @@
   */
  public void renameEntry(DN currentDN, Entry entry,
                          ModifyDNOperation modifyDNOperation)
       throws DirectoryException, CancelledOperationException
      throws DirectoryException, CancelledOperationException
  {
    writerBegin();
    EntryContainer currentContainer = rootContainer.getEntryContainer(
        currentDN);
    EntryContainer container = rootContainer.getEntryContainer(entry.getDN());
    if (currentContainer != container)
    {
      // FIXME: No reason why we cannot implement a move between containers
      // since the containers share the same database environment.
      int msgID = MSGID_JEB_FUNCTION_NOT_SUPPORTED;
      String msg = getMessage(MSGID_JEB_FUNCTION_NOT_SUPPORTED);
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                   msg, msgID);
    }
    try
    {
      EntryContainer currentContainer = rootContainer.getEntryContainer(
          currentDN);
      EntryContainer container = rootContainer.getEntryContainer(entry.getDN());
      if (currentContainer != container)
      {
        // FIXME: No reason why we cannot implement a move between containers
        // since the containers share the same database environment.
        int msgID = MSGID_JEB_FUNCTION_NOT_SUPPORTED;
        String msg = getMessage(MSGID_JEB_FUNCTION_NOT_SUPPORTED);
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                     msg, msgID);
      }
      currentContainer.sharedLock.lock();
      currentContainer.renameEntry(currentDN, entry, modifyDNOperation);
    }
@@ -900,6 +875,7 @@
    }
    finally
    {
      currentContainer.sharedLock.unlock();
      writerEnd();
    }
  }
@@ -916,13 +892,15 @@
   *          If a problem occurs while processing the search.
   */
  public void search(SearchOperation searchOperation)
       throws DirectoryException
      throws DirectoryException
  {
    readerBegin();
    EntryContainer ec = rootContainer.getEntryContainer(
        searchOperation.getBaseDN());
    ec.sharedLock.lock();
    try
    {
      EntryContainer ec = rootContainer.getEntryContainer(
          searchOperation.getBaseDN());
      ec.search(searchOperation);
    }
    catch (DatabaseException e)
@@ -937,6 +915,7 @@
    }
    finally
    {
      ec.sharedLock.unlock();
      readerEnd();
    }
  }
@@ -947,7 +926,7 @@
   * {@inheritDoc}
   */
  public void exportLDIF(LDIFExportConfig exportConfig)
       throws DirectoryException
      throws DirectoryException
  {
    // If the backend already has the root container open, we must use the same
    // underlying root container
@@ -955,16 +934,22 @@
    try
    {
      if (openRootContainer)
      if(openRootContainer)
      {
        // Open the database environment
        rootContainer = new RootContainer(config, this);
        rootContainer.open(config.getBackendDirectory(),
                           config.getBackendPermission(),
                           true, false, false, false, true, true);
        rootContainer.openEntryContainers(config.getBaseDNs());
        EnvironmentConfig envConfig =
            ConfigurableEnvironment.parseConfigEntry(cfg);
        envConfig.setReadOnly(true);
        envConfig.setAllowCreate(false);
        envConfig.setTransactional(false);
        envConfig.setTxnNoSync(false);
        envConfig.setConfigParam("je.env.isLocking", "true");
        envConfig.setConfigParam("je.env.runCheckpointer", "true");
        initializeRootContainer(envConfig);
      }
      ExportJob exportJob = new ExportJob(exportConfig);
      exportJob.exportLDIF(rootContainer);
    }
@@ -1010,6 +995,26 @@
                                   e.getMessage(),
                                   e.getMessageID());
    }
    catch (InitializationException ie)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ie);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   ie.getMessage(),
                                   ie.getMessageID());
    }
    catch (ConfigException ce)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ce);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   ce.getMessage(),
                                   ce.getMessageID());
    }
    finally
    {
      //If a root container was opened in this method as read only, close it
@@ -1038,12 +1043,63 @@
   * {@inheritDoc}
   */
  public void importLDIF(LDIFImportConfig importConfig)
       throws DirectoryException
      throws DirectoryException
  {
    // If the backend already has the root container open, we must use the same
    // underlying root container
    boolean openRootContainer = rootContainer == null;
    // If the rootContainer is open, the backend is initialized by something
    // else.
    // We can't do import while the backend is online.
    if(!openRootContainer)
    {
      String message = getMessage(MSGID_JEB_IMPORT_BACKEND_ONLINE);
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, MSGID_JEB_IMPORT_BACKEND_ONLINE);
    }
    try
    {
      ImportJob importJob = new ImportJob(this, config, importConfig);
      importJob.importLDIF();
      EnvironmentConfig envConfig =
          ConfigurableEnvironment.parseConfigEntry(cfg);
      /**
       envConfig.setConfigParam("je.env.runCleaner", "false");
       envConfig.setConfigParam("je.log.numBuffers", "2");
       envConfig.setConfigParam("je.log.bufferSize", "15000000");
       envConfig.setConfigParam("je.log.totalBufferBytes", "30000000");
       envConfig.setConfigParam("je.log.fileMax", "100000000");
       **/
      if (importConfig.appendToExistingData())
      {
        envConfig.setReadOnly(false);
        envConfig.setAllowCreate(true);
        envConfig.setTransactional(true);
        envConfig.setTxnNoSync(true);
        envConfig.setConfigParam("je.env.isLocking", "true");
        envConfig.setConfigParam("je.env.runCheckpointer", "false");
      }
      else
      {
        // We have the writer lock on the environment, now delete the
        // environment and re-open it. Only do this when we are
        // importing to all the base DNs in the backend.
        File backendDirectory = getFileForPath(cfg.getBackendDirectory());
        EnvManager.removeFiles(backendDirectory.getPath());
        envConfig.setReadOnly(false);
        envConfig.setAllowCreate(true);
        envConfig.setTransactional(false);
        envConfig.setTxnNoSync(false);
        envConfig.setConfigParam("je.env.isLocking", "false");
        envConfig.setConfigParam("je.env.runCheckpointer", "false");
      }
      initializeRootContainer(envConfig);
      ImportJob importJob = new ImportJob(importConfig);
      importJob.importLDIF(rootContainer);
    }
    catch (IOException ioe)
    {
@@ -1077,6 +1133,48 @@
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, MSGID_JEB_DATABASE_EXCEPTION);
    }
    catch (InitializationException ie)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ie);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   ie.getMessage(),
                                   ie.getMessageID());
    }
    catch (ConfigException ce)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ce);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   ce.getMessage(),
                                   ce.getMessageID());
    }
    finally
    {
      // leave the backend in the same state.
      try
      {
        rootContainer.close();
        rootContainer = null;
        // Sync the environment to disk.
        int msgID = MSGID_JEB_IMPORT_CLOSING_DATABASE;
        String message = getMessage(msgID);
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                 message, msgID);
      }
      catch (DatabaseException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
      }
    }
  }
@@ -1093,7 +1191,7 @@
   * @throws DirectoryException If a Directory Server error occurs.
   */
  public void verifyBackend(VerifyConfig verifyConfig, Entry statEntry)
       throws InitializationException, ConfigException, DirectoryException
      throws InitializationException, ConfigException, DirectoryException
  {
    // If the backend already has the root container open, we must use the same
    // underlying root container
@@ -1101,17 +1199,22 @@
    try
    {
      if (openRootContainer)
      if(openRootContainer)
      {
        // Open the database environment
        rootContainer = new RootContainer(config, this);
        rootContainer.open(config.getBackendDirectory(),
                           config.getBackendPermission(),
                           true, false, false, false, true, true);
        rootContainer.openEntryContainers(config.getBaseDNs());
        EnvironmentConfig envConfig =
            ConfigurableEnvironment.parseConfigEntry(cfg);
        envConfig.setReadOnly(true);
        envConfig.setAllowCreate(false);
        envConfig.setTransactional(false);
        envConfig.setTxnNoSync(false);
        envConfig.setConfigParam("je.env.isLocking", "true");
        envConfig.setConfigParam("je.env.runCheckpointer", "true");
        initializeRootContainer(envConfig);
      }
      VerifyJob verifyJob = new VerifyJob(config, verifyConfig);
      VerifyJob verifyJob = new VerifyJob(verifyConfig);
      verifyJob.verifyBackend(rootContainer, statEntry);
    }
    catch (DatabaseException e)
@@ -1170,7 +1273,7 @@
   * @throws DirectoryException If a Directory Server error occurs.
   */
  public void rebuildBackend(RebuildConfig rebuildConfig)
       throws InitializationException, ConfigException, DirectoryException
      throws InitializationException, ConfigException, DirectoryException
  {
    // If the backend already has the root container open, we must use the same
    // underlying root container
@@ -1191,10 +1294,10 @@
    {
      if (openRootContainer)
      {
        // Open the database environment
        rootContainer = new RootContainer(config, this);
        rootContainer.open();
        rootContainer.openEntryContainers(config.getBaseDNs());
        EnvironmentConfig envConfig =
            ConfigurableEnvironment.parseConfigEntry(cfg);
        initializeRootContainer(envConfig);
      }
      RebuildJob rebuildJob = new RebuildJob(rebuildConfig);
@@ -1249,10 +1352,10 @@
   * {@inheritDoc}
   */
  public void createBackup(BackupConfig backupConfig)
       throws DirectoryException
      throws DirectoryException
  {
    BackupManager backupManager =
         new BackupManager(getBackendID());
        new BackupManager(getBackendID());
    backupManager.createBackup(cfg, backupConfig);
  }
@@ -1262,10 +1365,10 @@
   * {@inheritDoc}
   */
  public void removeBackup(BackupDirectory backupDirectory, String backupID)
       throws DirectoryException
      throws DirectoryException
  {
    BackupManager backupManager =
         new BackupManager(getBackendID());
        new BackupManager(getBackendID());
    backupManager.removeBackup(backupDirectory, backupID);
  }
@@ -1275,10 +1378,10 @@
   * {@inheritDoc}
   */
  public void restoreBackup(RestoreConfig restoreConfig)
       throws DirectoryException
      throws DirectoryException
  {
    BackupManager backupManager =
         new BackupManager(getBackendID());
        new BackupManager(getBackendID());
    backupManager.restoreBackup(cfg, restoreConfig);
  }
@@ -1288,11 +1391,12 @@
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
       JEBackendCfg cfg,
       List<String> unacceptableReasons)
      JEBackendCfg cfg,
      List<String> unacceptableReasons)
  {
    // This listener handles only the changes to the base DNs.
    // The base DNs are checked by the backend config manager.
    return true;
  }
@@ -1301,74 +1405,73 @@
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(JEBackendCfg cfg)
  public ConfigChangeResult applyConfigurationChange(JEBackendCfg newCfg)
  {
    ConfigChangeResult ccr;
    ResultCode resultCode = ResultCode.SUCCESS;
    ArrayList<String> messages = new ArrayList<String>();
    try
    {
      DN[] baseDNs = new DN[cfg.getBackendBaseDN().size()];
      baseDNs = cfg.getBackendBaseDN().toArray(baseDNs);
      // Check for changes to the base DNs.
      for (DN baseDN : config.getBaseDNs())
      if(rootContainer != null)
      {
        boolean found = false;
        for (DN dn : baseDNs)
        {
          if (dn.equals(baseDN))
          {
            found = true;
          }
        }
        if (!found)
        {
          // The base DN was deleted.
          // FIXME This is not thread-safe.
          // Even though access to the entry container map is safe, there may be
          // operation threads with a handle on the entry container being
          // closed.
          DirectoryServer.deregisterBaseDN(baseDN, false);
          rootContainer.removeEntryContainer(baseDN);
        }
      }
        DN[] baseDNs = new DN[newCfg.getBackendBaseDN().size()];
        baseDNs = newCfg.getBackendBaseDN().toArray(baseDNs);
      for (DN baseDN : baseDNs)
      {
        if (!rootContainer.getBaseDNs().contains(baseDN))
        // Check for changes to the base DNs.
        for (DN baseDN : cfg.getBackendBaseDN())
        {
          try
          boolean found = false;
          for (DN dn : baseDNs)
          {
            // The base DN was added.
            rootContainer.openEntryContainer(baseDN);
            DirectoryServer.registerBaseDN(baseDN, this, false, false);
          }
          catch (Exception e)
          {
            if (debugEnabled())
            if (dn.equals(baseDN))
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
              found = true;
            }
          }
          if (!found)
          {
            // The base DN was deleted.
            DirectoryServer.deregisterBaseDN(baseDN, false);
            rootContainer.removeEntryContainer(baseDN);
          }
        }
            resultCode = DirectoryServer.getServerErrorResultCode();
        for (DN baseDN : baseDNs)
        {
          if (!rootContainer.getBaseDNs().contains(baseDN))
          {
            try
            {
              // The base DN was added.
              rootContainer.openEntryContainer(baseDN);
              DirectoryServer.registerBaseDN(baseDN, this, false, false);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            int msgID   = MSGID_BACKEND_CANNOT_REGISTER_BASEDN;
            messages.add(getMessage(msgID, String.valueOf(baseDN),
                                    String.valueOf(e)));
            ccr = new ConfigChangeResult(resultCode, false, messages);
            return ccr;
              resultCode = DirectoryServer.getServerErrorResultCode();
              int msgID   = MSGID_BACKEND_CANNOT_REGISTER_BASEDN;
              messages.add(getMessage(msgID, String.valueOf(baseDN),
                                      String.valueOf(e)));
              ccr = new ConfigChangeResult(resultCode, false, messages);
              return ccr;
            }
          }
        }
      }
      // Put the new configuration in place.
      this.cfg = cfg;
      this.cfg = newCfg;
    }
    catch (Exception e)
    {
      messages.add(e.getMessage());
      messages.add(StaticUtils.stackTraceToSingleLineString(e));
      ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
                                   false, messages);
      return ccr;
@@ -1399,10 +1502,31 @@
   * @throws  JebException     If an error occurs while removing the data.
   */
  public void clearBackend()
       throws ConfigException, JebException
      throws ConfigException, JebException
  {
    EnvManager.removeFiles(config.getBackendDirectory().getPath());
    // Determine the backend database directory.
    File backendDirectory = getFileForPath(cfg.getBackendDirectory());
    EnvManager.removeFiles(backendDirectory.getPath());
  }
  private void initializeRootContainer(EnvironmentConfig envConfig)
      throws ConfigException, InitializationException
  {
    // Open the database environment
    try
    {
      rootContainer = new RootContainer(this, cfg);
      rootContainer.open(envConfig);
    }
    catch (DatabaseException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      String message = getMessage(MSGID_JEB_OPEN_ENV_FAIL,
                                  e.getMessage());
      throw new InitializationException(MSGID_JEB_OPEN_ENV_FAIL, message, e);
    }
  }
}
opends/src/server/org/opends/server/backends/jeb/BackupManager.java
@@ -151,23 +151,6 @@
  public void createBackup(JEBackendCfg cfg, BackupConfig backupConfig)
       throws DirectoryException
  {
    // Parse our backend configuration.
    Config config = new Config();
    try
    {
      config.initializeConfig(cfg);
    }
    catch (ConfigException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessage(),
                                   e.getMessageID());
    }
    // Get the properties to use for the backup.
    String          backupID        = backupConfig.getBackupID();
    BackupDirectory backupDir       = backupConfig.getBackupDirectory();
@@ -249,7 +232,7 @@
    // If this is an incremental, determine the base backup for this backup.
    HashSet<String> dependencies = new HashSet<String>();
    BackupInfo baseBackup = null;
    File backendDir = config.getBackendDirectory();
    File backendDir = getFileForPath(cfg.getBackendDirectory());
/*
    FilenameFilter backupTagFilter = new FilenameFilter()
    {
@@ -764,26 +747,9 @@
    BackupInfo backupInfo = getBackupInfo(backupDir, backupID);
    // Parse our backend configuration.
    Config config = new Config();
    try
    {
      config.initializeConfig(cfg);
    }
    catch (ConfigException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessage(),
                                   e.getMessageID());
    }
    // Create a restore directory with a different name to the backend
    // directory.
    File currentDir = config.getBackendDirectory();
    File currentDir = getFileForPath(cfg.getBackendDirectory());
    File restoreDir = new File(currentDir.getPath() + "-restore-" + backupID);
    if (!verifyOnly)
    {
opends/src/server/org/opends/server/backends/jeb/Config.java
File was deleted
opends/src/server/org/opends/server/backends/jeb/DN2ID.java
@@ -26,28 +26,21 @@
 */
package org.opends.server.backends.jeb;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.*;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.util.StaticUtils;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import java.util.Comparator;
/**
 * This class represents the DN database, or dn2id, which has one record
 * for each entry.  The key is the normalized entry DN and the value
 * is the entry ID.
 */
public class DN2ID
public class DN2ID extends DatabaseContainer
{
  /**
   * The tracer object for the debug logger.
@@ -55,76 +48,46 @@
  private static final DebugTracer TRACER = getTracer();
  /**
   * The database entryContainer.
   * The key comparator used for the DN database.
   */
  private EntryContainer entryContainer;
  /**
   * The JE database configuration.
   */
  private DatabaseConfig dbConfig;
  /**
   * The name of the database within the entryContainer.
   */
  private String name;
  /**
   * A cached per-thread JE database handle.
   */
  private ThreadLocal<Database> threadLocalDatabase =
       new ThreadLocal<Database>();
  private Comparator<byte[]> dn2idComparator;
  /**
   * Create a DN2ID instance for the DN database in a given entryContainer.
   *
   * @param name The name of the DN database.
   * @param env The JE environment.
   * @param entryContainer The entryContainer of the DN database.
   * @param dbConfig The JE database configuration which will be used to
   * open the database.
   * @param name The name of the DN database ("dn2id").
   */
  public DN2ID(EntryContainer entryContainer, DatabaseConfig dbConfig,
               String name)
  {
    this.entryContainer = entryContainer;
    this.dbConfig = dbConfig.cloneConfig();
    this.name = name;
  }
  /**
   * Open the DN database.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void open() throws DatabaseException
  DN2ID(String name, Environment env, EntryContainer entryContainer)
      throws DatabaseException
  {
    getDatabase();
  }
    super(name, env, entryContainer);
  /**
   * Get a handle to the database. It returns a per-thread handle to avoid
   * any thread contention on the database handle. The entryContainer is
   * responsible for closing all handles.
   *
   * @return A database handle.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  private Database getDatabase() throws DatabaseException
  {
    Database database = threadLocalDatabase.get();
    if (database == null)
    dn2idComparator = new EntryContainer.KeyReverseComparator();
    DatabaseConfig dn2idConfig = new DatabaseConfig();
    if(env.getConfig().getReadOnly())
    {
      database = entryContainer.openDatabase(dbConfig, name);
      threadLocalDatabase.set(database);
      if(debugEnabled())
      {
        TRACER.debugInfo("JE DN2ID database %s opened with %d records.",
                  database.getDatabaseName(), database.count());
      }
      dn2idConfig.setReadOnly(true);
      dn2idConfig.setAllowCreate(false);
      dn2idConfig.setTransactional(false);
    }
    return database;
    else if(!env.getConfig().getTransactional())
    {
      dn2idConfig.setAllowCreate(true);
      dn2idConfig.setTransactional(false);
      dn2idConfig.setDeferredWrite(true);
    }
    else
    {
      dn2idConfig.setAllowCreate(true);
      dn2idConfig.setTransactional(true);
    }
    this.dbConfig = dn2idConfig;
    this.dbConfig.setBtreeComparator(dn2idComparator.getClass());
  }
  /**
@@ -157,7 +120,7 @@
    OperationStatus status;
    status = EntryContainer.insert(getDatabase(), txn, key, data);
    status = insert(txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -184,7 +147,7 @@
    DatabaseEntry data = id.getDatabaseEntry();
    OperationStatus status;
    status = EntryContainer.put(getDatabase(), txn, key, data);
    status = put(txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -207,7 +170,7 @@
       throws DatabaseException
  {
    OperationStatus status;
    status = EntryContainer.put(getDatabase(), txn, key, data);
    status = put(txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -229,7 +192,7 @@
  {
    DatabaseEntry key = DNdata(dn);
    OperationStatus status = EntryContainer.delete(getDatabase(), txn, key);
    OperationStatus status = delete(txn, key);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -252,8 +215,7 @@
    DatabaseEntry data = new DatabaseEntry();
    OperationStatus status;
    status = EntryContainer.read(getDatabase(), txn, key, data,
                                 LockMode.DEFAULT);
    status = read(txn, key, data, LockMode.DEFAULT);
    if (status != OperationStatus.SUCCESS)
    {
      return null;
@@ -262,93 +224,12 @@
  }
  /**
   * Open a JE cursor on the DN database.
   * @param txn A JE database transaction to be used by the cursor,
   * or null if none.
   * @param cursorConfig The JE cursor configuration.
   * @return A JE cursor.
   * @throws DatabaseException If an error occurs while attempting to open
   * the cursor.
   */
  public Cursor openCursor(Transaction txn, CursorConfig cursorConfig)
       throws DatabaseException
  {
    return getDatabase().openCursor(txn, cursorConfig);
  }
  /**
   * Removes all records from the database.
   * @param txn A JE database transaction to be used during the clear operation
   *            or null if not required. Using transactions increases the chance
   *            of lock contention.
   * @return The number of records removed.
   * @throws DatabaseException If an error occurs while cleaning the database.
   */
  public long clear(Transaction txn) throws DatabaseException
  {
    long deletedCount = 0;
    Cursor cursor = openCursor(txn, null);
    try
    {
      if(debugEnabled())
      {
        TRACER.debugVerbose("%d existing records will be deleted from the " +
            "database", getRecordCount());
      }
      DatabaseEntry data = new DatabaseEntry();
      DatabaseEntry key = new DatabaseEntry();
      OperationStatus status;
      // Step forward until we deleted all records.
      for (status = cursor.getFirst(key, data, LockMode.DEFAULT);
           status == OperationStatus.SUCCESS;
           status = cursor.getNext(key, data, LockMode.DEFAULT))
      {
        cursor.delete();
        deletedCount++;
      }
      if(debugEnabled())
      {
        TRACER.debugVerbose("%d records deleted", deletedCount);
      }
    }
    catch(DatabaseException de)
    {
      if(debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      throw de;
    }
    finally
    {
      cursor.close();
    }
    return deletedCount;
  }
  /**
   * Get the count of the number of entries stored.
   * Gets the comparator for records stored in this database.
   *
   * @return The number of entries stored.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   * @return The comparator for records stored in this database.
   */
  public long getRecordCount() throws DatabaseException
  public Comparator<byte[]> getComparator()
  {
    return EntryContainer.count(getDatabase());
    return dn2idComparator;
  }
  /**
   * Get a string representation of this object.
   * @return return A string representation of this object.
   */
  public String toString()
  {
    return name;
  }
}
opends/src/server/org/opends/server/backends/jeb/DN2URI.java
@@ -27,15 +27,7 @@
package org.opends.server.backends.jeb;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.*;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SearchOperation;
@@ -76,13 +68,18 @@
 * order is the same as in the DN database so that all referrals in a subtree
 * can be retrieved by cursoring through a range of the records.
 */
public class DN2URI
public class DN2URI extends DatabaseContainer
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The key comparator used for the DN database.
   */
  private Comparator<byte[]> dn2uriComparator;
  /**
   * The standard attribute type that is used to specify the set of referral
@@ -92,82 +89,45 @@
       DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
  /**
   * The database entryContainer.
   */
  private EntryContainer entryContainer;
  /**
   * The JE database configuration.
   */
  private DatabaseConfig dbConfig;
  /**
   * The name of the database within the entryContainer.
   */
  private String name;
  /**
   * A custom btree key comparator for the JE database.
   */
  Comparator<byte[]> comparator = new EntryContainer.KeyReverseComparator();
  /**
   * A cached per-thread JE database handle.
   */
  private ThreadLocal<Database> threadLocalDatabase =
       new ThreadLocal<Database>();
  /**
   * Create a new object representing a referral database in a given
   * entryContainer.
   *
   * @param entryContainer The entryContainer of the referral database.
   * @param dbConfig The JE database configuration which will be used to
   * open the database.
   * @param name The name of the referral database.
   */
  public DN2URI(EntryContainer entryContainer, DatabaseConfig dbConfig,
                String name)
  {
    this.entryContainer = entryContainer;
    this.dbConfig = dbConfig.cloneConfig();
    this.name = name;
  }
  /**
   * Open the referral database.
   *
   * @param env The JE environment.
   * @param entryContainer The entryContainer of the DN database.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void open() throws DatabaseException
  DN2URI(String name, Environment env,
        EntryContainer entryContainer)
      throws DatabaseException
  {
    getDatabase();
  }
    super(name, env, entryContainer);
  /**
   * Get a handle to the database. It returns a per-thread handle to avoid
   * any thread contention on the database handle. The entryContainer is
   * responsible for closing all handles.
   *
   * @return A database handle.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  private Database getDatabase() throws DatabaseException
  {
    Database database = threadLocalDatabase.get();
    if (database == null)
    dn2uriComparator = new EntryContainer.KeyReverseComparator();
    DatabaseConfig dn2uriConfig = new DatabaseConfig();
    if(env.getConfig().getReadOnly())
    {
      database = entryContainer.openDatabase(dbConfig, name);
      threadLocalDatabase.set(database);
      if(debugEnabled())
      {
        TRACER.debugInfo("JE DN2URI database %s opened with %d records.",
                  database.getDatabaseName(), database.count());
      }
      dn2uriConfig.setReadOnly(true);
      dn2uriConfig.setSortedDuplicates(true);
      dn2uriConfig.setAllowCreate(false);
      dn2uriConfig.setTransactional(false);
    }
    return database;
    else if(!env.getConfig().getTransactional())
    {
      dn2uriConfig.setSortedDuplicates(true);
      dn2uriConfig.setAllowCreate(true);
      dn2uriConfig.setTransactional(false);
      dn2uriConfig.setDeferredWrite(true);
    }
    else
    {
      dn2uriConfig.setSortedDuplicates(true);
      dn2uriConfig.setAllowCreate(true);
      dn2uriConfig.setTransactional(true);
    }
    this.dbConfig = dn2uriConfig;
    this.dbConfig.setBtreeComparator(dn2uriComparator.getClass());
  }
  /**
@@ -191,7 +151,7 @@
    // The JE insert method does not permit duplicate keys so we must use the
    // put method.
    status = EntryContainer.put(getDatabase(), txn, key, data);
    status = put(txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -216,7 +176,7 @@
    DatabaseEntry key = new DatabaseEntry(normDN);
    OperationStatus status;
    status = EntryContainer.delete(getDatabase(), txn, key);
    status = delete(txn, key);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -243,7 +203,7 @@
    DatabaseEntry data = new DatabaseEntry(URIBytes);
    OperationStatus status;
    Cursor cursor = getDatabase().openCursor(txn, cursorConfig);
    Cursor cursor = openCursor(txn, cursorConfig);
    try
    {
      status = cursor.getSearchBoth(key, data, null);
@@ -398,75 +358,6 @@
  }
  /**
   * Open a JE cursor on the DN database.
   * @param txn A JE database transaction to be used by the cursor,
   * or null if none.
   * @param cursorConfig The JE cursor configuration.
   * @return A JE cursor.
   * @throws DatabaseException If an error occurs while attempting to open
   * the cursor.
   */
  public Cursor openCursor(Transaction txn, CursorConfig cursorConfig)
       throws DatabaseException
  {
    return getDatabase().openCursor(txn, cursorConfig);
  }
    /**
   * Removes all records from the database.
   * @param txn A JE database transaction to be used during the clear operation
   *            or null if not required. Using transactions increases the chance
   *            of lock contention.
   * @return The number of records removed.
   * @throws DatabaseException If an error occurs while cleaning the database.
   */
  public long clear(Transaction txn) throws DatabaseException
  {
    long deletedCount = 0;
    Cursor cursor = openCursor(txn, null);
    try
    {
      if(debugEnabled())
      {
        TRACER.debugVerbose("%d existing records will be deleted from the " +
            "database", getRecordCount());
      }
      DatabaseEntry data = new DatabaseEntry();
      DatabaseEntry key = new DatabaseEntry();
      OperationStatus status;
      // Step forward until we deleted all records.
      for (status = cursor.getFirst(key, data, LockMode.DEFAULT);
           status == OperationStatus.SUCCESS;
           status = cursor.getNext(key, data, LockMode.DEFAULT))
      {
        cursor.delete();
        deletedCount++;
      }
      if(debugEnabled())
      {
        TRACER.debugVerbose("%d records deleted", deletedCount);
      }
    }
    catch(DatabaseException de)
    {
      if(debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      throw de;
    }
    finally
    {
      cursor.close();
    }
    return deletedCount;
  }
  /**
   * Checks whether the target of an operation is a referral entry and throws
   * a Directory referral exception if it is.
   * @param entry The target entry of the operation, or the base entry of a
@@ -593,7 +484,7 @@
    try
    {
      Cursor cursor = getDatabase().openCursor(txn, cursorConfig);
      Cursor cursor = openCursor(txn, cursorConfig);
      try
      {
        DatabaseEntry key = new DatabaseEntry();
@@ -684,7 +575,7 @@
    try
    {
      Cursor cursor = getDatabase().openCursor(txn, cursorConfig);
      Cursor cursor = openCursor(txn, cursorConfig);
      try
      {
        // Initialize the cursor very close to the starting value then
@@ -700,7 +591,7 @@
            end[0] = (byte) (end[0] + 1);
          }
          int cmp = comparator.compare(key.getData(), end);
          int cmp = dn2uriComparator.compare(key.getData(), end);
          if (cmp >= 0)
          {
            // We have gone past the ending value.
@@ -806,23 +697,12 @@
  }
  /**
   * Get the count of the number of entries stored.
   * Gets the comparator for records stored in this database.
   *
   * @return The number of entries stored.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   * @return The comparator used for records stored in this database.
   */
  public long getRecordCount() throws DatabaseException
  public Comparator<byte[]> getComparator()
  {
    return EntryContainer.count(getDatabase());
  }
  /**
   * Get a string representation of this object.
   * @return return A string representation of this object.
   */
  public String toString()
  {
    return name;
    return dn2uriComparator;
  }
}
opends/src/server/org/opends/server/backends/jeb/DatabaseContainer.java
New file
@@ -0,0 +1,388 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.backends.jeb;
import com.sleepycat.je.*;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
/**
 * This class is a wrapper around the JE database object and provides basic
 * read and write methods for entries.
 */
public abstract class DatabaseContainer
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The database entryContainer.
   */
  protected EntryContainer entryContainer;
  /**
   * The JE database configuration.
   */
  protected DatabaseConfig dbConfig;
  /**
   * The name of the database within the entryContainer.
   */
  protected String name;
  /**
   * The reference to the JE Environment.
   */
  private Environment env;
  /**
   * A list of JE database handles opened through this database
   * container.
   */
  private CopyOnWriteArrayList<Database> databases;
  /**
   * A cached per-thread JE database handle.
   */
  private ThreadLocal<Database> threadLocalDatabase =
      new ThreadLocal<Database>();
  /**
   * Create a new DatabaseContainer object.
   *
   * @param name The name of the entry database.
   * @param env The JE Environment.
   * @param entryContainer The entryContainer of the entry database.
   * @throws DatabaseException if a JE database error occurs.
   */
  protected DatabaseContainer(String name, Environment env,
                              EntryContainer entryContainer)
      throws DatabaseException
  {
    this.env = env;
    this.entryContainer = entryContainer;
    this.databases = new CopyOnWriteArrayList<Database>();
    StringBuilder builder = new StringBuilder();
    buildDatabaseName(builder, name);
    this.name = builder.toString();
  }
  /**
   * Opens a JE database in this database container. If the provided
   * database configuration is transactional, a transaction will be
   * created and used to perform the open.
   * <p>
   * Note that a database can be opened multiple times and will result in
   * multiple unique handles to the database.  This is used for example to
   * give each server thread its own database handle to eliminate contention
   * that could occur on a single handle.
   *
   * @return A new JE database handle.
   * @throws DatabaseException If an error occurs while attempting to open the
   * database.
   */
  private Database openDatabase() throws DatabaseException
  {
    Database database;
    if (dbConfig.getTransactional())
    {
      // Open the database under a transaction.
      Transaction txn =
          entryContainer.beginTransaction();
      try
      {
        database = env.openDatabase(txn, name, dbConfig);
        if (debugEnabled())
        {
          TRACER.debugVerbose("JE database %s opened with %d records. txnid=%d",
                              database.getDatabaseName(), database.count(),
                              txn.getId());
        }
        entryContainer.transactionCommit(txn);
      }
      catch (DatabaseException e)
      {
        entryContainer.transactionAbort(txn);
        throw e;
      }
    }
    else
    {
      database = env.openDatabase(null, name, dbConfig);
      if (debugEnabled())
      {
        TRACER.debugVerbose("JE database %s opened with %d records. txnid=none",
                            database.getDatabaseName(), database.count());
      }
    }
    return database;
  }
  private Database getDatabase() throws DatabaseException
  {
    Database database = threadLocalDatabase.get();
    if (database == null)
    {
      database = openDatabase();
      databases.add(database);
      threadLocalDatabase.set(database);
    }
    return database;
  }
  /**
   * Open the database container.
   *
   * @throws DatabaseException if a JE database error occurs while
   * openning the index.
   */
  public void open() throws DatabaseException
  {
    getDatabase();
  }
  /**
   * Constructs a full JE database name incorporating a entryContainer name.
   *
   * @param builder A string builder to which the full name will be appended.
   * @param name    The short database name.
   */
  private void buildDatabaseName(StringBuilder builder, String name)
  {
    builder.append(entryContainer.getContainerName());
    builder.append('_');
    builder.append(name);
  }
  /**
   * Flush any cached database information to disk and close the
   * database container.
   *
   * The database container should not be closed while other processes
   * aquired the container. The container should not be closed
   * while cursors handles into the database remain open, or
   * transactions that include operations on the database have not yet
   * been commited or aborted.
   *
   * The container may not be accessed again after this method is
   * called, regardless of the method's success or failure.
   *
   * @throws DatabaseException if an error occurs.
   */
  synchronized void close() throws DatabaseException
  {
    // Close each database handle that has been opened.
    for (Database database : databases)
    {
      if (database.getConfig().getDeferredWrite())
      {
        database.sync();
      }
      database.close();
    }
    if(debugEnabled())
    {
      TRACER.debugInfo("Closed database %s (%d handles)", name,
                       databases.size());
    }
    databases.clear();
    threadLocalDatabase = new ThreadLocal<Database>();
  }
  /**
   * Replace or insert a record into a JE database, with optional debug logging.
   * This is a simple wrapper around the JE Database.put method.
   * @param txn The JE transaction handle, or null if none.
   * @param key The record key.
   * @param data The record value.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  protected OperationStatus put(Transaction txn, DatabaseEntry key,
                                DatabaseEntry data)
      throws DatabaseException
  {
    Database database = getDatabase();
    OperationStatus status = database.put(txn, key, data);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status, database,
                           txn, key, data);
    }
    return status;
  }
  /**
   * Read a record from a JE database, with optional debug logging. This is a
   * simple wrapper around the JE Database.get method.
   * @param txn The JE transaction handle, or null if none.
   * @param key The key of the record to be read.
   * @param data The record value returned as output. Its byte array does not
   * need to be initialized by the caller.
   * @param lockMode The JE locking mode to be used for the read.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  protected OperationStatus read(Transaction txn,
                                 DatabaseEntry key, DatabaseEntry data,
                                 LockMode lockMode)
      throws DatabaseException
  {
    Database database = getDatabase();
    OperationStatus status = database.get(txn, key, data, lockMode);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status, database, txn, key,
                           data);
    }
    return status;
  }
  /**
   * Insert a record into a JE database, with optional debug logging. This is a
   * simple wrapper around the JE Database.putNoOverwrite method.
   * @param txn The JE transaction handle, or null if none.
   * @param key The record key.
   * @param data The record value.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  protected OperationStatus insert(Transaction txn,
                                   DatabaseEntry key, DatabaseEntry data)
      throws DatabaseException
  {
    Database database = getDatabase();
    OperationStatus status = database.putNoOverwrite(txn, key, data);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status, database, txn, key,
                           data);
    }
    return status;
  }
  /**
   * Delete a record from a JE database, with optional debug logging. This is a
   * simple wrapper around the JE Database.delete method.
   * @param txn The JE transaction handle, or null if none.
   * @param key The key of the record to be read.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  protected OperationStatus delete(Transaction txn,
                                   DatabaseEntry key)
      throws DatabaseException
  {
    Database database = getDatabase();
    OperationStatus status = database.delete(txn, key);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status, database, txn,
                           key, null);
    }
    return status;
  }
  /**
   * Open a JE cursor on the DN database.
   * @param txn A JE database transaction to be used by the cursor,
   * or null if none.
   * @param cursorConfig The JE cursor configuration.
   * @return A JE cursor.
   * @throws DatabaseException If an error occurs while attempting to open
   * the cursor.
   */
  public Cursor openCursor(Transaction txn, CursorConfig cursorConfig)
       throws DatabaseException
  {
    Database database = getDatabase();
    return database.openCursor(txn, cursorConfig);
  }
  /**
   * Get the count of key/data pairs in the database in a JE database.
   * This is a simple wrapper around the JE Database.count method.
   * @return The count of key/data pairs in the database.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  public long getRecordCount() throws DatabaseException
  {
    Database database = getDatabase();
    long count = database.count();
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, OperationStatus.SUCCESS,
                    database, null, null, null);
    }
    return count;
  }
  /**
   * Get a string representation of this object.
   * @return return A string representation of this object.
   */
  public String toString()
  {
    return name;
  }
  /**
   * Get the JE database name for this database container.
   *
   * @return JE database name for this database container.
   */
  public String getName()
  {
    return name;
  }
  /**
   * Preload the database into cache.
   *
   * @param config The preload configuration.
   * @return Statistics about the preload process.
   * @throws DatabaseException If an JE database error occurs
   * during the preload.
   */
  public PreloadStats preload(PreloadConfig config)
      throws DatabaseException
  {
    return getDatabase().preload(config);
  }
}
opends/src/server/org/opends/server/backends/jeb/DbPreloadComparator.java
@@ -26,11 +26,8 @@
 */
package org.opends.server.backends.jeb;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseException;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import java.util.Comparator;
@@ -38,7 +35,8 @@
 * This comparator is used to sort databases in order of priority
 * for preloading into the cache.
 */
public class DbPreloadComparator implements Comparator<Database>
public class DbPreloadComparator
    implements Comparator<DatabaseContainer>
{
  /**
   * The tracer object for the debug logger.
@@ -52,30 +50,19 @@
   * @param database A handle to the database.
   * @return 1 for id2entry database, 2 for dn2id database, 3 for all others.
   */
  static private int priority(Database database)
  static private int priority(DatabaseContainer database)
  {
    try
    String name = database.getName();
    if (name.endsWith(EntryContainer.ID2ENTRY_DATABASE_NAME))
    {
      String name = database.getDatabaseName();
      if (name.endsWith(EntryContainer.ID2ENTRY_DATABASE_NAME))
      {
        return 1;
      }
      else if (name.endsWith(EntryContainer.DN2ID_DATABASE_NAME))
      {
        return 2;
      }
      else
      {
        return 3;
      }
      return 1;
    }
    catch (DatabaseException e)
    else if (name.endsWith(EntryContainer.DN2ID_DATABASE_NAME))
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      return 2;
    }
    else
    {
      return 3;
    }
  }
@@ -91,7 +78,7 @@
   *         first argument is less than, equal to, or greater than the
   *         second.
   **/
  public int compare(Database database1, Database database2)
  public int compare(DatabaseContainer database1, DatabaseContainer database2)
  {
    return priority(database1) - priority(database2);
  }
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -48,20 +48,21 @@
import org.opends.server.util.StaticUtils;
import org.opends.server.util.ServerConstants;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.JebMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.util.ServerConstants.*;
import org.opends.server.admin.std.server.JEBackendCfg;
import org.opends.server.admin.std.server.JEIndexCfg;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.config.ConfigException;
/**
 * Storage container for LDAP entries.  Each base DN of a JE backend is given
@@ -69,6 +70,9 @@
 * the guts of the backend API methods for LDAP operations.
 */
public class EntryContainer
    implements ConfigurationChangeListener<JEBackendCfg>,
    ConfigurationAddListener<JEIndexCfg>,
    ConfigurationDeleteListener<JEIndexCfg>
{
  /**
   * The tracer object for the debug logger.
@@ -102,6 +106,11 @@
  public static final String REFERRAL_DATABASE_NAME = "referral";
  /**
   * The name of the state database.
   */
  public static final String STATE_DATABASE_NAME = "state";
  /**
   * The attribute used to return a search index debug string to the client.
   */
  public static final String ATTR_DEBUG_SEARCH_INDEX = "debugsearchindex";
@@ -124,13 +133,7 @@
  /**
   * The backend configuration.
   */
  private Config config;
  /**
   * A list of JE database handles opened through this entryContainer.
   * They will be closed by the entryContainer.
   */
  private ArrayList<Database> databases;
  private JEBackendCfg config;
  /**
   * The JE database environment.
@@ -143,21 +146,11 @@
  private DN2ID dn2id;
  /**
   * The key comparator used for the DN database.
   */
  private Comparator<byte[]> dn2idComparator;
  /**
   * The entry database maps an entry ID (8 bytes) to a complete encoded entry.
   */
  private ID2Entry id2entry;
  /**
   * Compression and cryptographic options for the entry database.
   */
  private DataConfig entryDataConfig;
  /**
   * Index maps entry ID to an entry ID list containing its children.
   */
  private Index id2children;
@@ -173,11 +166,32 @@
  private DN2URI dn2uri;
  /**
   * The state database maps a config DN to config entries.
   */
  private State state;
  /**
   * The set of attribute indexes.
   */
  private HashMap<AttributeType, AttributeIndex> attrIndexMap;
  /**
   * Cached value from config so they don't have to be retrieved per operation.
   */
  private int deadlockRetryLimit;
  private int subtreeDeleteSizeLimit;
  private int indexEntryLimit;
  /**
   * A read write lock to handle schema changes and bulk changes.
   */
  private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  final Lock sharedLock = lock.readLock();
  final Lock exclusiveLock = lock.writeLock();
  /**
   * Create a new entry entryContainer object.
   *
   * @param baseDN  The baseDN this entry container will be responsible for
@@ -188,215 +202,89 @@
   * @param config The configuration of the JE backend.
   * @param env The JE environment to create this entryContainer in.
   * @param rootContainer The root container this entry container is in.
   * @throws ConfigException if a configuration related error occurs.
   */
  public EntryContainer(DN baseDN, Backend backend, Config config,
  public EntryContainer(DN baseDN, Backend backend, JEBackendCfg config,
                        Environment env, RootContainer rootContainer)
      throws ConfigException
  {
    this.backend = backend;
    this.baseDN = baseDN;
    this.config = config;
    this.env = env;
    this.rootContainer = rootContainer;
    // Instantiate the list of database handles.
    databases = new ArrayList<Database>();
    // Instantiate indexes for id2children and id2subtree.
    id2children = new Index(this, ID2CHILDREN_DATABASE_NAME,
                            new ID2CIndexer(),
                            config.getBackendIndexEntryLimit(),
                            0);
    id2subtree = new Index(this, ID2SUBTREE_DATABASE_NAME,
                           new ID2SIndexer(),
                           config.getBackendIndexEntryLimit(),
                           0);
    this.deadlockRetryLimit = config.getBackendDeadlockRetryLimit();
    this.subtreeDeleteSizeLimit = config.getBackendSubtreeDeleteSizeLimit();
    this.indexEntryLimit = config.getBackendIndexEntryLimit();
    // Instantiate the attribute indexes.
    attrIndexMap = new HashMap<AttributeType, AttributeIndex>();
    if (config != null && config.getIndexConfigMap() != null)
    {
      for (IndexConfig indexConfig : config.getIndexConfigMap().values())
      {
        AttributeIndex index = new AttributeIndex(this, indexConfig);
        attrIndexMap.put(indexConfig.getAttributeType(), index);
      }
    }
    entryDataConfig = new DataConfig();
    entryDataConfig.setCompressed(config.isEntriesCompressed());
    config.addJEChangeListener(this);
    config.addJEIndexAddListener(this);
    config.addJEIndexDeleteListener(this);
  }
  /**
   * Opens the entryContainer for reading and writing.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   * @throws ConfigException if a configuration related error occurs.
   */
  public void open()
       throws DatabaseException
      throws DatabaseException, ConfigException
  {
    // Use this database config, duplicates are not allowed.
    DatabaseConfig dbNodupsConfig = new DatabaseConfig();
    dbNodupsConfig.setAllowCreate(true);
    dbNodupsConfig.setTransactional(true);
    try
    {
      id2entry = new ID2Entry(this, dbNodupsConfig, entryDataConfig,
                              ID2ENTRY_DATABASE_NAME);
      DataConfig entryDataConfig = new DataConfig();
      entryDataConfig.setCompressed(config.isBackendEntriesCompressed());
      id2entry = new ID2Entry(ID2ENTRY_DATABASE_NAME, entryDataConfig,
                              env, this);
      id2entry.open();
      // Set the dn2id ordering so that we can iterate through a subtree.
      dn2idComparator = new KeyReverseComparator();
      DatabaseConfig dn2idConfig = new DatabaseConfig();
      dn2idConfig.setAllowCreate(true);
      dn2idConfig.setTransactional(true);
      dn2idConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2id = new DN2ID(this, dn2idConfig, DN2ID_DATABASE_NAME);
      dn2id = new DN2ID(DN2ID_DATABASE_NAME, env, this);
      dn2id.open();
      id2children.open(dbNodupsConfig);
      id2subtree.open(dbNodupsConfig);
      state = new State(STATE_DATABASE_NAME, env, this);
      state.open();
      DatabaseConfig dn2uriConfig = new DatabaseConfig();
      dn2uriConfig.setSortedDuplicates(true);
      dn2uriConfig.setAllowCreate(true);
      dn2uriConfig.setTransactional(true);
      dn2uriConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2uri = new DN2URI(this, dn2uriConfig, REFERRAL_DATABASE_NAME);
      id2children = new Index(ID2CHILDREN_DATABASE_NAME,
                              new ID2CIndexer(), state,
                              indexEntryLimit, 0,
                              env,this);
      id2children.open();
      id2subtree = new Index(ID2SUBTREE_DATABASE_NAME,
                             new ID2SIndexer(), state,
                             indexEntryLimit, 0,
                             env, this);
      id2subtree.open();
      dn2uri = new DN2URI(REFERRAL_DATABASE_NAME, env, this);
      dn2uri.open();
      for (AttributeIndex index : attrIndexMap.values())
      for (String idx : config.listJEIndexes())
      {
        index.open(dbNodupsConfig);
        JEIndexCfg indexCfg = config.getJEIndex(idx);
        //TODO: When issue 1793 is fixed, use inherited default values in
        //admin framework instead for the entry limit.
        AttributeIndex index =
            new AttributeIndex(indexCfg, state,
                               indexEntryLimit,
                               env, this);
        index.open();
        attrIndexMap.put(indexCfg.getIndexAttribute(), index);
      }
    }
    catch (DatabaseException e)
    catch (DatabaseException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      close();
      throw e;
    }
  }
  /**
   * Opens the entryContainer for reading and writing without transactions.
   *
   * @param  deferredWrite  Indicates whether to open the entryContainer using
   *                        the deferred write mode.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void openNonTransactional(boolean deferredWrite)
       throws DatabaseException
  {
    // Use this database config, duplicates are not allowed.
    DatabaseConfig dbNodupsConfig = new DatabaseConfig();
    dbNodupsConfig.setAllowCreate(true);
    dbNodupsConfig.setTransactional(false);
    dbNodupsConfig.setDeferredWrite(deferredWrite);
    try
    {
      id2entry = new ID2Entry(this, dbNodupsConfig, entryDataConfig,
                              ID2ENTRY_DATABASE_NAME);
      id2entry.open();
      // Set the dn2id ordering so that we can iterate through a subtree.
      dn2idComparator = new KeyReverseComparator();
      DatabaseConfig dn2idConfig = new DatabaseConfig();
      dn2idConfig.setAllowCreate(true);
      dn2idConfig.setTransactional(false);
      dn2idConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2idConfig.setDeferredWrite(deferredWrite);
      dn2id = new DN2ID(this, dn2idConfig, DN2ID_DATABASE_NAME);
      dn2id.open();
      id2children.open(dbNodupsConfig);
      id2subtree.open(dbNodupsConfig);
      DatabaseConfig dn2uriConfig = new DatabaseConfig();
      dn2uriConfig.setSortedDuplicates(true);
      dn2uriConfig.setAllowCreate(true);
      dn2uriConfig.setTransactional(false);
      dn2uriConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2uriConfig.setDeferredWrite(deferredWrite);
      dn2uri = new DN2URI(this, dn2uriConfig, REFERRAL_DATABASE_NAME);
      dn2uri.open();
      for (AttributeIndex index : attrIndexMap.values())
      {
        index.open(dbNodupsConfig);
      }
    }
    catch (DatabaseException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      close();
      throw e;
    }
  }
  /**
   * Opens the entryContainer for reading only.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void openReadOnly()
       throws DatabaseException
  {
    // Use this database config, duplicates are not allowed.
    DatabaseConfig dbNodupsConfig = new DatabaseConfig();
    dbNodupsConfig.setReadOnly(true);
    dbNodupsConfig.setAllowCreate(false);
    dbNodupsConfig.setTransactional(false);
    try
    {
      id2entry = new ID2Entry(this, dbNodupsConfig, entryDataConfig,
                              ID2ENTRY_DATABASE_NAME);
      id2entry.open();
      // Set the dn2id ordering so that we can iterate through a subtree.
      dn2idComparator = new KeyReverseComparator();
      DatabaseConfig dn2idConfig = new DatabaseConfig();
      dn2idConfig.setReadOnly(true);
      dn2idConfig.setAllowCreate(false);
      dn2idConfig.setTransactional(false);
      dn2idConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2id = new DN2ID(this, dn2idConfig, DN2ID_DATABASE_NAME);
      dn2id.open();
      id2children.open(dbNodupsConfig);
      id2subtree.open(dbNodupsConfig);
      DatabaseConfig dn2uriConfig = new DatabaseConfig();
      dn2uriConfig.setReadOnly(true);
      dn2uriConfig.setSortedDuplicates(true);
      dn2uriConfig.setAllowCreate(false);
      dn2uriConfig.setTransactional(false);
      dn2uriConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2uri = new DN2URI(this, dn2uriConfig, REFERRAL_DATABASE_NAME);
      dn2uri.open();
      for (AttributeIndex index : attrIndexMap.values())
      {
        index.open(dbNodupsConfig);
      }
    }
    catch (DatabaseException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      close();
      throw e;
      throw de;
    }
  }
@@ -406,23 +294,93 @@
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void close()
       throws DatabaseException
      throws DatabaseException
  {
    // Close each database handle that has been opened.
    for (Database database : databases)
    try
    {
      if (database.getConfig().getDeferredWrite())
      dn2id.close();
    }
    catch (DatabaseNotFoundException e)
    {
      if (debugEnabled())
      {
        database.sync();
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      database.close();
    }
    try
    {
      id2entry.close();
    }
    catch (DatabaseNotFoundException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    try
    {
      id2children.close();
    }
    catch (DatabaseNotFoundException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    try
    {
      id2subtree.close();
    }
    catch (DatabaseNotFoundException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    try
    {
      state.close();
    }
    catch (DatabaseNotFoundException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    try
    {
      dn2uri.close();
    }
    catch (DatabaseNotFoundException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    for (AttributeIndex index : attrIndexMap.values())
    {
      index.close();
      try
      {
        index.close();
      }
      catch (DatabaseNotFoundException e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
    config.removeJEChangeListener(this);
    config.removeJEIndexAddListener(this);
    config.removeJEIndexDeleteListener(this);
  }
  /**
@@ -492,6 +450,17 @@
  }
  /**
   * Retrieve all attribute indexes.
   *
   * @return All attribute indexes defined in this entry container.
   */
  public Collection<AttributeIndex> getAttributeIndexes()
  {
    return attrIndexMap.values();
  }
  /**
   * Determine the highest entryID in the entryContainer.
   * The entryContainer must already be open.
   *
@@ -1021,7 +990,7 @@
              lookthroughLimit));
            return;
          }
          int cmp = dn2idComparator.compare(key.getData(), end);
          int cmp = dn2id.getComparator().compare(key.getData(), end);
          if (cmp >= 0)
          {
            // We have gone past the ending value.
@@ -1398,10 +1367,10 @@
   * @throws JebException If an error occurs in the JE backend.
   */
  public void addEntry(Entry entry, AddOperation addOperation)
       throws DatabaseException, DirectoryException, JebException
      throws DatabaseException, DirectoryException, JebException
  {
    TransactedOperation operation =
         new AddEntryTransaction(entry);
        new AddEntryTransaction(entry);
    invokeTransactedOperation(operation);
  }
@@ -1418,11 +1387,11 @@
   * @throws JebException If an error occurs in the JE backend.
   */
  private void invokeTransactedOperation(TransactedOperation operation)
       throws DatabaseException, DirectoryException, JebException
      throws DatabaseException, DirectoryException, JebException
  {
    // Attempt the operation under a transaction until it fails or completes.
    boolean completed = false;
    int retryRemaining = config.getDeadlockRetryLimit();
    int retryRemaining = deadlockRetryLimit;
    while (!completed)
    {
      // Start a transaction.
@@ -1506,7 +1475,7 @@
     * @throws JebException If an error occurs in the JE backend.
     */
    public abstract void invokeOperation(Transaction txn)
         throws DatabaseException, DirectoryException, JebException;
        throws DatabaseException, DirectoryException, JebException;
    /**
     * This method is called after the transaction has successfully
@@ -1568,7 +1537,7 @@
     * @throws JebException If an error occurs in the JE backend.
     */
    public void invokeOperation(Transaction txn)
         throws DatabaseException, DirectoryException, JebException
        throws DatabaseException, DirectoryException, JebException
    {
      // Check whether the entry already exists.
      if (dn2id.get(txn, entry.getDN()) != null)
@@ -1700,10 +1669,10 @@
   * @throws JebException If an error occurs in the JE backend.
   */
  public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
       throws DirectoryException, DatabaseException, JebException
      throws DirectoryException, DatabaseException, JebException
  {
    DeleteEntryTransaction operation =
         new DeleteEntryTransaction(entryDN, deleteOperation);
        new DeleteEntryTransaction(entryDN, deleteOperation);
    invokeTransactedOperation(operation);
@@ -1712,9 +1681,9 @@
      String message = getMessage(MSGID_JEB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED,
                                  operation.getDeletedEntryCount());
      throw new DirectoryException(
           ResultCode.ADMIN_LIMIT_EXCEEDED,
           message,
           MSGID_JEB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED);
          ResultCode.ADMIN_LIMIT_EXCEEDED,
          message,
          MSGID_JEB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED);
    }
    String message = getMessage(MSGID_JEB_DELETED_ENTRY_COUNT,
@@ -1745,7 +1714,7 @@
                          Transaction txn,
                          DN leafDN,
                          EntryID leafID)
       throws DatabaseException, DirectoryException, JebException
      throws DatabaseException, DirectoryException, JebException
  {
    // Check that the entry exists in id2entry and read its contents.
    Entry entry = id2entry.get(txn, leafID);
@@ -1859,7 +1828,7 @@
                            BufferedIndex id2sBuffered,
                            Transaction txn,
                            DN leafDN)
       throws DatabaseException, DirectoryException, JebException
      throws DatabaseException, DirectoryException, JebException
  {
    // Read the entry ID from dn2id.
    EntryID leafID = dn2id.get(txn, leafDN);
@@ -2015,13 +1984,13 @@
     * @throws JebException If an error occurs in the JE backend.
     */
    public void invokeOperation(Transaction txn)
         throws DatabaseException, DirectoryException, JebException
        throws DatabaseException, DirectoryException, JebException
    {
      // Check for referral entries above the target entry.
      dn2uri.targetEntryReferrals(entryDN, null);
      // Determine whether this is a subtree delete.
      int adminSizeLimit = config.getSubtreeDeleteSizeLimit();
      int adminSizeLimit = subtreeDeleteSizeLimit;
      boolean isSubtreeDelete = false;
      List<Control> controls = deleteOperation.getRequestControls();
      if (controls != null)
@@ -2077,7 +2046,7 @@
        // Step back until the key is less than the beginning value
        while (status == OperationStatus.SUCCESS &&
             dn2idComparator.compare(key.getData(), begin) >= 0)
            dn2id.getComparator().compare(key.getData(), begin) >= 0)
        {
          status = cursor.getPrev(key, data, LockMode.DEFAULT);
        }
@@ -2085,7 +2054,7 @@
        // Step back until we pass the ending value.
        while (status == OperationStatus.SUCCESS)
        {
          int cmp = dn2idComparator.compare(key.getData(), end);
          int cmp = dn2id.getComparator().compare(key.getData(), end);
          if (cmp < 0)
          {
            // We have gone past the ending value.
@@ -2196,7 +2165,7 @@
   *                              determination.
   */
  public boolean entryExists(DN entryDN)
         throws DirectoryException
      throws DirectoryException
  {
    EntryCache entryCache = DirectoryServer.getEntryCache();
@@ -2241,7 +2210,7 @@
   * @throws DatabaseException An error occurred during a database operation.
   */
  public Entry getEntry(DN entryDN)
       throws JebException, DatabaseException, DirectoryException
      throws JebException, DatabaseException, DirectoryException
  {
    EntryCache entryCache = DirectoryServer.getEntryCache();
    Entry entry = null;
@@ -2643,11 +2612,11 @@
   */
  public void renameEntry(DN currentDN, Entry entry,
                          ModifyDNOperation modifyDNOperation)
         throws DatabaseException, JebException, DirectoryException,
                CancelledOperationException
      throws DatabaseException, JebException, DirectoryException,
      CancelledOperationException
  {
    TransactedOperation operation =
         new RenameEntryTransaction(currentDN, entry, modifyDNOperation);
        new RenameEntryTransaction(currentDN, entry, modifyDNOperation);
    invokeTransactedOperation(operation);
  }
@@ -2703,7 +2672,7 @@
     * @param modifyDNOperation The Modify DN operation to be performed.
     */
    public RenameEntryTransaction(DN currentDN, Entry entry,
                                   ModifyDNOperation modifyDNOperation)
                                  ModifyDNOperation modifyDNOperation)
    {
      this.oldApexDN = currentDN;
      this.oldSuperiorDN = getParentWithinBase(currentDN);
@@ -2721,8 +2690,8 @@
     * @throws JebException If an error occurs in the JE backend.
     */
    public void invokeOperation(Transaction txn) throws DatabaseException,
                                                        DirectoryException,
                                                        JebException
        DirectoryException,
        JebException
    {
      DN requestedNewSuperiorDN = null;
@@ -2810,7 +2779,7 @@
       * downwards.
       */
      byte[] suffix = StaticUtils.getBytes("," +
                                           oldApexDN.toNormalizedString());
          oldApexDN.toNormalizedString());
      /*
       * Set the ending value to a value of equal length but slightly
@@ -2835,7 +2804,7 @@
        // Step forward until the key is greater than the starting value.
        while (status == OperationStatus.SUCCESS &&
             dn2idComparator.compare(key.getData(), begin) <= 0)
            dn2id.getComparator().compare(key.getData(), begin) <= 0)
        {
          status = cursor.getNext(key, data, LockMode.RMW);
        }
@@ -2843,7 +2812,7 @@
        // Step forward until we pass the ending value.
        while (status == OperationStatus.SUCCESS)
        {
          int cmp = dn2idComparator.compare(key.getData(), end);
          int cmp = dn2id.getComparator().compare(key.getData(), end);
          if (cmp >= 0)
          {
            // We have gone past the ending value.
@@ -2920,7 +2889,7 @@
    private void moveApexEntry(Transaction txn,
                               EntryID oldID, EntryID newID,
                               Entry oldEntry, Entry newEntry)
         throws JebException, DirectoryException, DatabaseException
        throws JebException, DirectoryException, DatabaseException
    {
      DN oldDN = oldEntry.getDN();
      DN newDN = newEntry.getDN();
@@ -2975,7 +2944,7 @@
      if (newParentDN != null)
      {
        EntryID parentID = dn2id.get(txn, newParentDN);
        id2cBuffered.insertID(config.getBackendIndexEntryLimit(),
        id2cBuffered.insertID(indexEntryLimit,
                              parentID.getDatabaseEntry().getData(),
                              newID);
      }
@@ -2993,7 +2962,7 @@
      for (DN dn = newParentDN; dn != null; dn = getParentWithinBase(dn))
      {
        EntryID nodeID = dn2id.get(txn, dn);
        id2sBuffered.insertID(config.getBackendIndexEntryLimit(),
        id2sBuffered.insertID(indexEntryLimit,
                              nodeID.getDatabaseEntry().getData(), newID);
      }
@@ -3026,7 +2995,7 @@
     */
    private void renameApexEntry(Transaction txn, EntryID entryID,
                                 Entry oldEntry, Entry newEntry)
         throws DirectoryException, DatabaseException, JebException
        throws DirectoryException, DatabaseException, JebException
    {
      DN oldDN = oldEntry.getDN();
      DN newDN = newEntry.getDN();
@@ -3089,7 +3058,7 @@
    private void moveSubordinateEntry(Transaction txn,
                                      EntryID oldID, EntryID newID,
                                      Entry oldEntry, DN newDN)
         throws JebException, DirectoryException, DatabaseException
        throws JebException, DirectoryException, DatabaseException
    {
      DN oldDN = oldEntry.getDN();
      DN newParentDN = getParentWithinBase(newDN);
@@ -3143,7 +3112,7 @@
        if (newParentDN != null)
        {
          EntryID parentID = dn2id.get(txn, newParentDN);
          id2cBuffered.insertID(config.getBackendIndexEntryLimit(),
          id2cBuffered.insertID(indexEntryLimit,
                                parentID.getDatabaseEntry().getData(),
                                newID);
        }
@@ -3163,7 +3132,7 @@
        if (!newID.equals(oldID) || dn.isAncestorOf(newSuperiorDN))
        {
          EntryID nodeID = dn2id.get(txn, dn);
          id2sBuffered.insertID(config.getBackendIndexEntryLimit(),
          id2sBuffered.insertID(indexEntryLimit,
                                nodeID.getDatabaseEntry().getData(), newID);
        }
      }
@@ -3188,8 +3157,8 @@
     * @throws DatabaseException If an error occurs in the JE database.
     */
    private void renameSubordinateEntry(Transaction txn, EntryID entryID,
                                 Entry oldEntry, DN newDN)
         throws DirectoryException, DatabaseException
                                        Entry oldEntry, DN newDN)
        throws DirectoryException, DatabaseException
    {
      DN oldDN = oldEntry.getDN();
@@ -3324,7 +3293,7 @@
   * @throws JebException If an error occurs in the JE backend.
   */
  private void indexInsertEntry(Transaction txn, Entry entry, EntryID entryID)
       throws DatabaseException, DirectoryException, JebException
      throws DatabaseException, DirectoryException, JebException
  {
    for (AttributeIndex index : attrIndexMap.values())
    {
@@ -3343,7 +3312,7 @@
   * @throws JebException If an error occurs in the JE backend.
   */
  private void indexRemoveEntry(Transaction txn, Entry entry, EntryID entryID)
       throws DatabaseException, DirectoryException, JebException
      throws DatabaseException, DirectoryException, JebException
  {
    for (AttributeIndex index : attrIndexMap.values())
    {
@@ -3365,7 +3334,7 @@
  private void indexModifications(Transaction txn, Entry oldEntry,
                                  Entry newEntry,
                                  EntryID entryID, List<Modification> mods)
       throws DatabaseException
      throws DatabaseException
  {
    // Process in index configuration order.
    for (AttributeIndex index : attrIndexMap.values())
@@ -3410,7 +3379,7 @@
  {
    try
    {
      removeDatabase(DN2ID_DATABASE_NAME);
      removeDatabase(dn2id);
    }
    catch (DatabaseNotFoundException e)
    {
@@ -3421,7 +3390,7 @@
    }
    try
    {
      removeDatabase(ID2ENTRY_DATABASE_NAME);
      removeDatabase(id2entry);
    }
    catch (DatabaseNotFoundException e)
    {
@@ -3432,7 +3401,7 @@
    }
    try
    {
      removeDatabase(ID2CHILDREN_DATABASE_NAME);
      removeDatabase(id2children);
    }
    catch (DatabaseNotFoundException e)
    {
@@ -3443,7 +3412,18 @@
    }
    try
    {
      removeDatabase(ID2SUBTREE_DATABASE_NAME);
      removeDatabase(id2subtree);
    }
    catch (DatabaseNotFoundException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    try
    {
      removeDatabase(state);
    }
    catch (DatabaseNotFoundException e)
    {
@@ -3456,7 +3436,7 @@
    {
      try
      {
        index.removeIndex();
        removeAttributeIndex(index);
      }
      catch (DatabaseNotFoundException e)
      {
@@ -3466,6 +3446,7 @@
        }
      }
    }
    attrIndexMap.clear();
  }
  /**
@@ -3486,33 +3467,20 @@
  }
  /**
   * Get a list of the databases opened by this entryContainer.  There will be
   * only one handle in the list for each database, regardless of the number
   * of handles open for a given database.
   * @param dbList A list of JE database handles.
   * Get a list of the databases opened by this entryContainer.
   * @param dbList A list of database containers.
   */
  public void listDatabases(List<Database> dbList)
  public void listDatabases(List<DatabaseContainer> dbList)
  {
    // There may be more than one handle open for a given database
    // so we eliminate duplicates here.
    HashSet<String> set = new HashSet<String>();
    for (Database db : databases)
    dbList.add(dn2id);
    dbList.add(id2entry);
    dbList.add(dn2uri);
    dbList.add(id2children);
    dbList.add(id2subtree);
    for(AttributeIndex index : attrIndexMap.values())
    {
      try
      {
        if (!set.contains(db.getDatabaseName()))
        {
          set.add(db.getDatabaseName());
          dbList.add(db);
        }
      }
      catch (DatabaseException e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
      index.listDatabases(dbList);
    }
  }
@@ -3556,72 +3524,6 @@
  }
  /**
   * Opens a JE database in this entryContainer. The resulting database handle
   * must not be closed by the caller, as it will be closed by the
   * entryContainer. If the provided database configuration is transactional,
   * a transaction will be created and used to perform the open.
   * <p>
   * Note that a database can be opened multiple times and will result in
   * multiple unique handles to the database.  This is used for example to
   * give each server thread its own database handle to eliminate contention
   * that could occur on a single handle.
   *
   * @param dbConfig The JE database configuration to be used to open the
   * database.
   * @param name     The short database name, to which the entryContainer name
   *                 will be added.
   * @return A new JE database handle.
   * @throws DatabaseException If an error occurs while attempting to open the
   * database.
   */
  public synchronized Database openDatabase(DatabaseConfig dbConfig,
                                            String name)
       throws DatabaseException
  {
    Database database;
    StringBuilder builder = new StringBuilder();
    buildDatabaseName(builder, name);
    String fullName = builder.toString();
    if (dbConfig.getTransactional())
    {
      // Open the database under a transaction.
      Transaction txn = beginTransaction();
      try
      {
        database = env.openDatabase(txn, fullName, dbConfig);
        if (debugEnabled())
        {
          TRACER.debugVerbose("open db=%s txnid=%d",
                              database.getDatabaseName(),
                       txn.getId());
        }
        transactionCommit(txn);
      }
      catch (DatabaseException e)
      {
        transactionAbort(txn);
        throw e;
      }
    }
    else
    {
      database = env.openDatabase(null, fullName, dbConfig);
      if (debugEnabled())
      {
        TRACER.debugVerbose("open db=%s txnid=none",
                            database.getDatabaseName());
      }
    }
    // Insert into the list of database handles.
    databases.add(database);
    return database;
  }
  /**
   * Begin a leaf transaction using the default configuration.
   * Provides assertion debug logging.
   * @return A JE transaction handle.
@@ -3629,7 +3531,7 @@
   * a new transaction.
   */
  public Transaction beginTransaction()
       throws DatabaseException
      throws DatabaseException
  {
    Transaction parentTxn = null;
    TransactionConfig txnConfig = null;
@@ -3649,7 +3551,7 @@
   * the transaction.
   */
  public static void transactionCommit(Transaction txn)
       throws DatabaseException
      throws DatabaseException
  {
    if (txn != null)
    {
@@ -3669,7 +3571,7 @@
   * transaction.
   */
  public static void transactionAbort(Transaction txn)
       throws DatabaseException
      throws DatabaseException
  {
    if (txn != null)
    {
@@ -3682,205 +3584,59 @@
  }
  /**
   * Insert a record into a JE database, with optional debug logging. This is a
   * simple wrapper around the JE Database.putNoOverwrite method.
   * @param database The JE database handle.
   * @param txn The JE transaction handle, or null if none.
   * @param key The record key.
   * @param data The record value.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  public static OperationStatus insert(Database database, Transaction txn,
                                DatabaseEntry key, DatabaseEntry data)
       throws DatabaseException
  {
    OperationStatus status = database.putNoOverwrite(txn, key, data);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status, database, txn, key,
                           data);
    }
    return status;
  }
  /**
   * Insert a record into a JE database through a cursor, with optional debug
   * logging. This is a simple wrapper around the JE Cursor.putNoOverwrite
   * method.
   * @param cursor The JE cursor handle.
   * @param key The record key.
   * @param data The record value.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  public static OperationStatus cursorInsert(Cursor cursor,
                                             DatabaseEntry key,
                                             DatabaseEntry data)
       throws DatabaseException
  {
    OperationStatus status = cursor.putNoOverwrite(key, data);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status,
                    cursor.getDatabase(), null, key, data);
    }
    return status;
  }
  /**
   * Replace or insert a record into a JE database, with optional debug logging.
   * This is a simple wrapper around the JE Database.put method.
   * @param database The JE database handle.
   * @param txn The JE transaction handle, or null if none.
   * @param key The record key.
   * @param data The record value.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  public static OperationStatus put(Database database, Transaction txn,
                                    DatabaseEntry key, DatabaseEntry data)
       throws DatabaseException
  {
    OperationStatus status = database.put(txn, key, data);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status, database, txn, key,
                           data);
    }
    return status;
  }
  /**
   * Replace or insert a record into a JE database through a cursor, with
   * optional debug logging. This is a simple wrapper around the JE Cursor.put
   * method.
   * @param cursor The JE cursor handle.
   * @param key The record key.
   * @param data The record value.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  public static OperationStatus cursorPut(Cursor cursor,
                                          DatabaseEntry key,
                                          DatabaseEntry data)
       throws DatabaseException
  {
    OperationStatus status = cursor.put(key, data);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status,
                    cursor.getDatabase(), null, key, data);
    }
    return status;
  }
  /**
   * Read a record from a JE database, with optional debug logging. This is a
   * simple wrapper around the JE Database.get method.
   * @param database The JE database handle.
   * @param txn The JE transaction handle, or null if none.
   * @param key The key of the record to be read.
   * @param data The record value returned as output. Its byte array does not
   * need to be initialized by the caller.
   * @param lockMode The JE locking mode to be used for the read.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  public static OperationStatus read(Database database, Transaction txn,
                              DatabaseEntry key, DatabaseEntry data,
                              LockMode lockMode)
       throws DatabaseException
  {
    OperationStatus status = database.get(txn, key, data, lockMode);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status, database, txn, key,
                           data);
    }
    return status;
  }
  /**
   * Read a record from a JE database through a cursor, with optional debug
   * logging. This is a simple wrapper around the JE Cursor.getSearchKey method.
   * @param cursor The JE cursor handle.
   * @param key The key of the record to be read.
   * @param data The record value returned as output. Its byte array does not
   * need to be initialized by the caller.
   * @param lockMode The JE locking mode to be used for the read.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  public static OperationStatus cursorRead(Cursor cursor,
                                           DatabaseEntry key,
                                           DatabaseEntry data,
                                           LockMode lockMode)
       throws DatabaseException
  {
    OperationStatus status = cursor.getSearchKey(key, data, lockMode);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status,
                    cursor.getDatabase(), null, key, data);
    }
    return status;
  }
  /**
   * Delete a record from a JE database, with optional debug logging. This is a
   * simple wrapper around the JE Database.delete method.
   * @param database The JE database handle.
   * @param txn The JE transaction handle, or null if none.
   * @param key The key of the record to be read.
   * @return The operation status.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  public static OperationStatus delete(Database database, Transaction txn,
                                       DatabaseEntry key)
       throws DatabaseException
  {
    OperationStatus status = database.delete(txn, key);
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, status, database, txn, key,
                           null);
    }
    return status;
  }
  /**
   * Get the count of key/data pairs in the database in a JE database.
   * This is a simple wrapper around the JE Database.count method.
   * @param database the JE database handle.
   * @return The count of key/data pairs in the database.
   * @throws DatabaseException If an error occurs in the JE operation.
   */
  public static long count(Database database) throws DatabaseException
  {
    long count = database.count();
    if (debugEnabled())
    {
      TRACER.debugJEAccess(DebugLogLevel.VERBOSE, OperationStatus.SUCCESS,
                    database, null, null, null);
    }
    return count;
  }
  /**
   * Remove a database from disk.
   *
   * @param name The short database name, to which the entryContainer name will
   * be added.
   * @param database The database container to remove.
   * @throws DatabaseException If an error occurs while attempting to delete the
   * database.
   */
  public void removeDatabase(String name) throws DatabaseException
  public void removeDatabase(DatabaseContainer database)
      throws DatabaseException
  {
    StringBuilder builder = new StringBuilder();
    buildDatabaseName(builder, name);
    String fullName = builder.toString();
    env.removeDatabase(null, fullName);
    database.close();
    env.removeDatabase(null, database.getName());
    if(database instanceof Index)
    {
      state.removeIndexTrustState(null, (Index)database);
    }
  }
  /**
   * Removes a attribute index from disk.
   *
   * @param index The attribute index to remove.
   * @throws DatabaseException If an JE database error occurs while attempting
   * to delete the index.
   */
  public void removeAttributeIndex(AttributeIndex index)
      throws DatabaseException
  {
    index.close();
    if(index.equalityIndex != null)
    {
      env.removeDatabase(null, index.equalityIndex.getName());
      state.removeIndexTrustState(null, index.equalityIndex);
    }
    if(index.presenceIndex != null)
    {
      env.removeDatabase(null, index.presenceIndex.getName());
      state.removeIndexTrustState(null, index.presenceIndex);
    }
    if(index.substringIndex != null)
    {
      env.removeDatabase(null, index.substringIndex.getName());
      state.removeIndexTrustState(null, index.substringIndex);
    }
    if(index.orderingIndex != null)
    {
      env.removeDatabase(null, index.orderingIndex.getName());
      state.removeIndexTrustState(null, index.orderingIndex);
    }
    if(index.approximateIndex != null)
    {
      env.removeDatabase(null, index.approximateIndex.getName());
      state.removeIndexTrustState(null, index.approximateIndex);
    }
  }
  /**
@@ -3934,4 +3690,235 @@
    return dn.getParent();
  }
  /**
   * {@inheritDoc}
   */
  public synchronized boolean isConfigurationChangeAcceptable(
      JEBackendCfg cfg, List<String> unacceptableReasons)
  {
    // This is always true because only all config attributes used
    // by the entry container should be validated by the admin framework.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized ConfigChangeResult applyConfigurationChange(
      JEBackendCfg cfg)
  {
    boolean adminActionRequired = false;
    ArrayList<String> messages = new ArrayList<String>();
    if(config.getBackendIndexEntryLimit() != cfg.getBackendIndexEntryLimit())
    {
      for(AttributeIndex index : attrIndexMap.values())
      {
        if(index.setBackendIndexEntryLimit(cfg.getBackendIndexEntryLimit()))
        {
          adminActionRequired = true;
          int msgID = MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD;
          String message = getMessage(msgID,
                                      index.getAttributeType().getNameOrOID());
          messages.add(message);
        }
        index.setBackendIndexEntryLimit(cfg.getBackendIndexEntryLimit());
      }
      if(id2children.setIndexEntryLimit(cfg.getBackendIndexEntryLimit()))
      {
        adminActionRequired = true;
        int msgID = MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD;
        String message = getMessage(msgID, id2children.getName());
        messages.add(message);
      }
      if(id2subtree.setIndexEntryLimit(cfg.getBackendIndexEntryLimit()))
      {
        adminActionRequired = true;
        int msgID = MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD;
        String message = getMessage(msgID, id2subtree.getName());
        messages.add(message);
      }
    }
    DataConfig entryDataConfig = new DataConfig();
    entryDataConfig.setCompressed(cfg.isBackendEntriesCompressed());
    id2entry.setDataConfig(entryDataConfig);
    this.config = cfg;
    this.deadlockRetryLimit = config.getBackendDeadlockRetryLimit();
    this.subtreeDeleteSizeLimit = config.getBackendSubtreeDeleteSizeLimit();
    this.indexEntryLimit = config.getBackendIndexEntryLimit();
    return new ConfigChangeResult(ResultCode.SUCCESS,
                                  adminActionRequired, messages);
  }
  /**
   * {@inheritDoc}
   */
  public synchronized boolean isConfigurationAddAcceptable(
      JEIndexCfg cfg, List<String> unacceptableReasons)
  {
    // TODO: validate more before returning true?
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized ConfigChangeResult applyConfigurationAdd(JEIndexCfg cfg)
  {
    ConfigChangeResult ccr;
    boolean adminActionRequired = false;
    ArrayList<String> messages = new ArrayList<String>();
    try
    {
      AttributeIndex index =
          new AttributeIndex(cfg, state,
                             indexEntryLimit,
                             env, this);
      index.open();
      attrIndexMap.put(cfg.getIndexAttribute(), index);
    }
    catch(Exception e)
    {
      messages.add(StaticUtils.stackTraceToSingleLineString(e));
      ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
                                   adminActionRequired,
                                   messages);
      return ccr;
    }
    adminActionRequired = true;
    int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
    messages.add(getMessage(msgID, cfg.getIndexAttribute().getNameOrOID()));
    return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
                                  messages);
  }
  /**
   * {@inheritDoc}
   */
  public synchronized boolean isConfigurationDeleteAcceptable(
      JEIndexCfg cfg, List<String> unacceptableReasons)
  {
    // TODO: validate more before returning true?
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized ConfigChangeResult applyConfigurationDelete(
      JEIndexCfg cfg)
  {
    ConfigChangeResult ccr;
    boolean adminActionRequired = false;
    ArrayList<String> messages = new ArrayList<String>();
    exclusiveLock.lock();
    try
    {
      AttributeIndex index = attrIndexMap.get(cfg.getIndexAttribute());
      removeAttributeIndex(index);
      attrIndexMap.remove(cfg.getIndexAttribute());
    }
    catch(DatabaseException de)
    {
      messages.add(StaticUtils.stackTraceToSingleLineString(de));
      ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
                                   adminActionRequired,
                                   messages);
      return ccr;
    }
    finally
    {
      exclusiveLock.unlock();
    }
    return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
                                  messages);
  }
  /**
   * Get the environment config of the JE environment used in this entry
   * container.
   *
   * @return The environment config of the JE environment.
   * @throws DatabaseException If an error occurs while retriving the
   *                           configuration object.
   */
  public EnvironmentConfig getEnvironmentConfig()
      throws DatabaseException
  {
    return env.getConfig();
  }
  /**
   * Clear the contents for a database from disk.
   *
   * @param txn A transaction object or null if its not required.
   * @param database The database to clear.
   * @return The number of records deleted.
   * @throws DatabaseException if a JE database error occurs.
   */
  public long clearDatabase(Transaction txn, DatabaseContainer database)
      throws DatabaseException
  {
    database.close();
    long count = env.truncateDatabase(txn, database.getName(), true);
    database.open();
    if(debugEnabled())
    {
      TRACER.debugVerbose("Cleared %d existing records from the " +
          "database %s", count, database.getName());
    }
    return count;
  }
  /**
   * Clear the contents for a attribute index from disk.
   *
   * @param txn A transaction object or null if its not required.
   * @param index The attribute index to clear.
   * @return The number of records deleted.
   * @throws DatabaseException if a JE database error occurs.
   */
  public long clearAttributeIndex(Transaction txn, AttributeIndex index)
      throws DatabaseException
  {
    long count = 0;
    index.close();
    if(index.equalityIndex != null)
    {
      count += env.truncateDatabase(txn, index.equalityIndex.getName(), true);
    }
    if(index.presenceIndex != null)
    {
      count += env.truncateDatabase(txn, index.presenceIndex.getName(), true);
    }
    if(index.substringIndex != null)
    {
      count += env.truncateDatabase(txn, index.substringIndex.getName(), true);
    }
    if(index.orderingIndex != null)
    {
      count += env.truncateDatabase(txn, index.orderingIndex.getName(), true);
    }
    if(index.approximateIndex != null)
    {
      count += env.truncateDatabase(txn, index.approximateIndex.getName(),
                                    true);
    }
    index.open();
    if(debugEnabled())
    {
      TRACER.debugVerbose("Cleared %d existing records from the " +
          "index %s", count, index.getAttributeType().getNameOrOID());
    }
    return count;
  }
}
opends/src/server/org/opends/server/backends/jeb/EqualityIndexer.java
@@ -66,20 +66,20 @@
       new AttributeIndex.KeyComparator();
  /**
   * The attribute index configuration for which this instance will
   * The attribute type for which this instance will
   * generate index keys.
   */
  private IndexConfig indexConfig;
  private AttributeType attributeType;
  /**
   * Create a new attribute equality indexer for the given index configuration.
   * @param indexConfig The index configuration for which an indexer is
   * Create a new attribute equality indexer for the given attribute type.
   * @param attributeType The attribute type for which an indexer is
   * required.
   */
  public EqualityIndexer(IndexConfig indexConfig)
  public EqualityIndexer(AttributeType attributeType)
  {
    this.indexConfig = indexConfig;
    this.attributeType = attributeType;
  }
  /**
@@ -89,7 +89,7 @@
   */
  public String toString()
  {
    return indexConfig.getAttributeType().getNameOrOID() + ".equality";
    return attributeType.getNameOrOID() + ".equality";
  }
  /**
@@ -118,7 +118,7 @@
                         Set<ASN1OctetString> keys) throws DatabaseException
  {
    List<Attribute> attrList =
         entry.getAttribute(indexConfig.getAttributeType());
         entry.getAttribute(attributeType);
    if (attrList != null)
    {
      indexAttribute(attrList, keys);
@@ -146,11 +146,11 @@
  {
    List<Attribute> attrList;
    attrList = oldEntry.getAttribute(indexConfig.getAttributeType());
    attrList = oldEntry.getAttribute(attributeType);
    Set<ASN1OctetString> oldSet = new HashSet<ASN1OctetString>();
    indexAttribute(attrList, oldSet);
    attrList = newEntry.getAttribute(indexConfig.getAttributeType());
    attrList = newEntry.getAttribute(attributeType);
    Set<ASN1OctetString> newSet = new HashSet<ASN1OctetString>();
    indexAttribute(attrList, newSet);
@@ -198,8 +198,8 @@
    List<Attribute> beforeList;
    List<Attribute> afterList;
    beforeList = oldEntry.getAttribute(indexConfig.getAttributeType());
    afterList = newEntry.getAttribute(indexConfig.getAttributeType());
    beforeList = oldEntry.getAttribute(attributeType);
    afterList = newEntry.getAttribute(attributeType);
    boolean hasOptions = false;
@@ -233,7 +233,7 @@
    {
      Attribute modAttr = mod.getAttribute();
      AttributeType modAttrType = modAttr.getAttributeType();
      if (modAttrType.equals(indexConfig.getAttributeType()))
      if (modAttrType.equals(attributeType))
      {
        if (modAttr.hasOptions())
        {
opends/src/server/org/opends/server/backends/jeb/ExportJob.java
@@ -145,7 +145,15 @@
    {
      for (EntryContainer exportContainer : exportContainers)
      {
        exportContainer(exportContainer);
        exportContainer.sharedLock.lock();
        try
        {
          exportContainer(exportContainer);
        }
        finally
        {
          exportContainer.sharedLock.unlock();
        }
      }
    }
    finally
opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
@@ -31,15 +31,7 @@
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.JebMessages.*;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.*;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
@@ -49,7 +41,7 @@
 * the entry ID and the value is the entry contents.
 *
 */
public class ID2Entry
public class ID2Entry extends DatabaseContainer
{
  /**
   * The tracer object for the debug logger.
@@ -57,83 +49,49 @@
  private static final DebugTracer TRACER = getTracer();
  /**
   * The database entryContainer.
   */
  private EntryContainer entryContainer;
  /**
   * The JE database configuration.
   */
  private DatabaseConfig dbConfig;
  /**
   * Parameters for compression and encryption.
   */
  private DataConfig dataConfig;
  /**
   * The name of the database within the entryContainer.
   */
  private String name;
  /**
   * A cached per-thread JE database handle.
   */
  private ThreadLocal<Database> threadLocalDatabase =
       new ThreadLocal<Database>();
  /**
   * Create a new ID2Entry object.
   * @param entryContainer The entryContainer of the entry database.
   * @param dbConfig The JE database configuration to be used to open the
   * underlying JE database.
   *
   * @param name The name of the entry database.
   * @param dataConfig The desired compression and encryption options for data
   * stored in the entry database.
   * @param name The name of the entry database.
   * @param env The JE Environment.
   * @param entryContainer The entryContainer of the entry database.
   * @throws DatabaseException If an error occurs in the JE database.
   *
   */
  public ID2Entry(EntryContainer entryContainer, DatabaseConfig dbConfig,
                  DataConfig dataConfig, String name)
  ID2Entry(String name, DataConfig dataConfig,
           Environment env, EntryContainer entryContainer)
      throws DatabaseException
  {
    this.entryContainer = entryContainer;
    this.dbConfig = dbConfig.cloneConfig();
    this.name = name;
    super(name, env, entryContainer);
    this.dataConfig = dataConfig;
  }
  /**
   * Open the entry database.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void open() throws DatabaseException
  {
    getDatabase();
  }
    DatabaseConfig dbNodupsConfig = new DatabaseConfig();
  /**
   * Get a handle to the database. It returns a per-thread handle to avoid
   * any thread contention on the database handle. The entryContainer is
   * responsible for closing all handles.
   *
   * @return A database handle.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  private Database getDatabase() throws DatabaseException
  {
    Database database = threadLocalDatabase.get();
    if (database == null)
    if(env.getConfig().getReadOnly())
    {
      database = entryContainer.openDatabase(dbConfig, name);
      threadLocalDatabase.set(database);
      if(debugEnabled())
      {
        TRACER.debugInfo("JE ID2Entry database %s opened with %d records.",
                  database.getDatabaseName(), database.count());
      }
      dbNodupsConfig.setReadOnly(true);
      dbNodupsConfig.setAllowCreate(false);
      dbNodupsConfig.setTransactional(false);
    }
    return database;
    else if(!env.getConfig().getTransactional())
    {
      dbNodupsConfig.setAllowCreate(true);
      dbNodupsConfig.setTransactional(false);
      dbNodupsConfig.setDeferredWrite(true);
    }
    else
    {
      dbNodupsConfig.setAllowCreate(true);
      dbNodupsConfig.setTransactional(true);
    }
    this.dbConfig = dbNodupsConfig;
  }
  /**
@@ -172,7 +130,7 @@
    DatabaseEntry data = entryData(entry);
    OperationStatus status;
    status = EntryContainer.insert(getDatabase(), txn, key, data);
    status = insert(txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -198,7 +156,7 @@
    DatabaseEntry data = entryData(entry);
    OperationStatus status;
    status = EntryContainer.put(getDatabase(), txn, key, data);
    status = put(txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -219,7 +177,7 @@
       throws DatabaseException
  {
    OperationStatus status;
    status = EntryContainer.put(getDatabase(), txn, key, data);
    status = put(txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -240,7 +198,7 @@
  {
    DatabaseEntry key = id.getDatabaseEntry();
    OperationStatus status = EntryContainer.delete(getDatabase(), txn, key);
    OperationStatus status = delete(txn, key);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -264,7 +222,7 @@
    DatabaseEntry data = new DatabaseEntry();
    OperationStatus status;
    status = EntryContainer.read(getDatabase(), txn, key, data,
    status = read(txn, key, data,
                                 LockMode.DEFAULT);
    if (status != OperationStatus.SUCCESS)
@@ -311,38 +269,14 @@
  }
  /**
   * Open a database cursor on the entry database.
   * Set the desired compression and encryption options for data
   * stored in the entry database.
   *
   * @param txn The database transaction, or null if none.
   * @param cursorConfig The JE cursor configuration.
   * @return A new cursor.
   * @throws DatabaseException If an error occurs in the JE database.
   * @param dataConfig The desired compression and encryption options for data
   * stored in the entry database.
   */
  public Cursor openCursor(Transaction txn, CursorConfig cursorConfig)
       throws DatabaseException
  public void setDataConfig(DataConfig dataConfig)
  {
    Database database = getDatabase();
    return database.openCursor(txn, cursorConfig);
  }
  /**
   * Get the count of the number of entries stored.
   *
   * @return The number of entries stored.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public long getRecordCount() throws DatabaseException
  {
    return EntryContainer.count(getDatabase());
  }
  /**
   * Get a string representation of this object.
   * @return return A string representation of this object.
   */
  public String toString()
  {
    return name;
    this.dataConfig = dataConfig;
  }
}
opends/src/server/org/opends/server/backends/jeb/ImportContext.java
@@ -30,6 +30,7 @@
import org.opends.server.types.Entry;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.util.LDIFReader;
import org.opends.server.admin.std.server.JEBackendCfg;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
@@ -53,7 +54,7 @@
  /**
   * The configuration of the destination backend.
   */
  private Config config;
  private JEBackendCfg config;
  /**
   * The requested LDIF import configuration.
@@ -159,7 +160,7 @@
   * Set the configuration of the destination backend.
   * @param config The destination backend configuration.
   */
  public void setConfig(Config config)
  public void setConfig(JEBackendCfg config)
  {
    this.config = config;
  }
@@ -168,7 +169,7 @@
   * Get the configuration of the destination backend.
   * @return The destination backend configuration.
   */
  public Config getConfig()
  public JEBackendCfg getConfig()
  {
    return config;
  }
opends/src/server/org/opends/server/backends/jeb/ImportJob.java
@@ -31,10 +31,8 @@
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.Transaction;
import org.opends.server.api.Backend;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.messages.JebMessages;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
@@ -44,25 +42,29 @@
import org.opends.server.types.ResultCode;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.StaticUtils;
import static org.opends.server.util.StaticUtils.getFileForPath;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import static org.opends.server.messages.JebMessages.
     MSGID_JEB_IMPORT_ENTRY_EXISTS;
    MSGID_JEB_IMPORT_ENTRY_EXISTS;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.JebMessages.
     MSGID_JEB_IMPORT_PARENT_NOT_FOUND;
    MSGID_JEB_IMPORT_PARENT_NOT_FOUND;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.messages.JebMessages.*;
import org.opends.server.admin.std.server.JEBackendCfg;
/**
 * Import from LDIF to a JE backend.
@@ -75,14 +77,9 @@
  private static final DebugTracer TRACER = getTracer();
  /**
   * The backend instance we are importing into.
   */
  private Backend backend;
  /**
   * The JE backend configuration.
   */
  private Config config;
  private JEBackendCfg config;
  /**
   * The root container used for this import job.
@@ -103,7 +100,7 @@
   * Map of base DNs to their import context.
   */
  private HashMap<DN,ImportContext> importMap =
       new HashMap<DN, ImportContext>();
      new HashMap<DN, ImportContext>();
  /**
   * The number of entries imported.
@@ -116,19 +113,21 @@
   */
  private long progressInterval = 10000;
  /**
   * The import worker threads.
   */
  private CopyOnWriteArrayList<ImportThread> threads;
  /**
   * Create a new import job.
   *
   * @param backend The backend performing the import.
   * @param config The backend configuration.
   * @param ldifImportConfig The LDIF import configuration.
   */
  public ImportJob(Backend backend, Config config,
                   LDIFImportConfig ldifImportConfig)
  public ImportJob(LDIFImportConfig ldifImportConfig)
  {
    this.backend = backend;
    this.config = config;
    this.ldifImportConfig = ldifImportConfig;
    this.threads = new CopyOnWriteArrayList<ImportThread>();
  }
  /**
@@ -138,61 +137,32 @@
   * processes the LDIF file, then merges the resulting intermediate
   * files to load the index databases.
   *
   * @param rootContainer The root container to import into.
   * @throws DatabaseException If an error occurs in the JE database.
   * @throws IOException  If a problem occurs while opening the LDIF file for
   *                      reading, or while reading from the LDIF file.
   * @throws JebException If an error occurs in the JE backend.
   */
  public void importLDIF()
  public void importLDIF(RootContainer rootContainer)
      throws DatabaseException, IOException, JebException
  {
/*
    envConfig.setConfigParam("je.env.runCleaner", "false");
    envConfig.setConfigParam("je.log.numBuffers", "2");
    envConfig.setConfigParam("je.log.bufferSize", "15000000");
    envConfig.setConfigParam("je.log.totalBufferBytes", "30000000");
    envConfig.setConfigParam("je.log.fileMax", "100000000");
*/
    // Create an LDIF reader. Throws an exception if the file does not exist.
    reader = new LDIFReader(ldifImportConfig);
    this.rootContainer = rootContainer;
    this.config = rootContainer.getConfiguration();
    int msgID;
    String message;
    long startTime;
    try
    {
      rootContainer = new RootContainer(config, backend);
      if (ldifImportConfig.appendToExistingData())
      {
        rootContainer.open(config.getBackendDirectory(),
                           config.getBackendPermission(),
                           false, true, true, true, true, false);
      }
      else
      {
        rootContainer.open(config.getBackendDirectory(),
                           config.getBackendPermission(),
                           false, true, false, false, false, false);
      }
      if (!ldifImportConfig.appendToExistingData())
      {
        // We have the writer lock on the environment, now delete the
        // environment and re-open it. Only do this when we are
        // importing to all the base DNs in the backend.
        rootContainer.close();
        EnvManager.removeFiles(config.getBackendDirectory().getPath());
        rootContainer.open(config.getBackendDirectory(),
                           config.getBackendPermission(),
                           false, true, false, false, false, false);
      }
      // Divide the total buffer size by the number of threads
      // and give that much to each thread.
      int importThreadCount = config.getImportThreadCount();
      long bufferSize = config.getImportBufferSize() /
           (importThreadCount*config.getBaseDNs().length);
      int importThreadCount = config.getBackendImportThreadCount();
      long bufferSize = config.getBackendImportBufferSize() /
          (importThreadCount*rootContainer.getBaseDNs().size());
      msgID = MSGID_JEB_IMPORT_THREAD_COUNT;
      message = getMessage(msgID, importThreadCount);
@@ -211,10 +181,7 @@
               message, msgID);
      TRACER.debugInfo(
        rootContainer.getEnvironmentConfig().toString());
      rootContainer.openEntryContainers(config.getBaseDNs());
          rootContainer.getEnvironmentConfig().toString());
      // Create the import contexts for each base DN.
      DN baseDN;
@@ -237,7 +204,7 @@
        // Create an entry queue.
        LinkedBlockingQueue<Entry> queue =
             new LinkedBlockingQueue<Entry>(config.getImportQueueSize());
            new LinkedBlockingQueue<Entry>(config.getBackendImportQueueSize());
        importContext.setQueue(queue);
        importMap.put(baseDN, importContext);
@@ -246,77 +213,70 @@
      // Make a note of the time we started.
      startTime = System.currentTimeMillis();
      // Create a temporary work directory.
      File tempDir = getFileForPath(config.getBackendImportTempDirectory());
      if(!tempDir.exists() && !tempDir.mkdir())
      {
        msgID = MSGID_JEB_IMPORT_CREATE_TMPDIR_ERROR;
        String msg = getMessage(msgID, tempDir);
        throw new IOException(msg);
      }
      if (tempDir.listFiles() != null)
      {
        for (File f : tempDir.listFiles())
        {
          f.delete();
        }
      }
      try
      {
        // Create a temporary work directory.
        File tempDir = new File(config.getImportTempDirectory());
        tempDir.mkdir();
        if (tempDir.listFiles() != null)
        importedCount = 0;
        int     passNumber = 1;
        boolean moreData   = true;
        while (moreData)
        {
          for (File f : tempDir.listFiles())
          moreData = processLDIF();
          if (moreData)
          {
            f.delete();
            msgID = MSGID_JEB_IMPORT_BEGINNING_INTERMEDIATE_MERGE;
            message = getMessage(msgID, passNumber++);
            logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                     message, msgID);
          }
        }
        try
        {
          importedCount = 0;
          int     passNumber = 1;
          boolean moreData   = true;
          while (moreData)
          else
          {
            moreData = processLDIF();
            if (moreData)
            {
              msgID = MSGID_JEB_IMPORT_BEGINNING_INTERMEDIATE_MERGE;
              message = getMessage(msgID, passNumber++);
              logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                       message, msgID);
            }
            else
            {
              msgID = MSGID_JEB_IMPORT_BEGINNING_FINAL_MERGE;
              message = getMessage(msgID);
              logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                       message, msgID);
            }
            long mergeStartTime = System.currentTimeMillis();
            merge();
            long mergeEndTime = System.currentTimeMillis();
            if (moreData)
            {
              msgID = MSGID_JEB_IMPORT_RESUMING_LDIF_PROCESSING;
              message = getMessage(msgID, ((mergeEndTime-mergeStartTime)/1000));
              logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                       message, msgID);
            }
            else
            {
              msgID = MSGID_JEB_IMPORT_FINAL_MERGE_COMPLETED;
              message = getMessage(msgID, ((mergeEndTime-mergeStartTime)/1000));
              logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                       message, msgID);
            }
            msgID = MSGID_JEB_IMPORT_BEGINNING_FINAL_MERGE;
            message = getMessage(msgID);
            logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                     message, msgID);
          }
        }
        finally
        {
          tempDir.delete();
          long mergeStartTime = System.currentTimeMillis();
          merge();
          long mergeEndTime = System.currentTimeMillis();
          if (moreData)
          {
            msgID = MSGID_JEB_IMPORT_RESUMING_LDIF_PROCESSING;
            message = getMessage(msgID, ((mergeEndTime-mergeStartTime)/1000));
            logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                     message, msgID);
          }
          else
          {
            msgID = MSGID_JEB_IMPORT_FINAL_MERGE_COMPLETED;
            message = getMessage(msgID, ((mergeEndTime-mergeStartTime)/1000));
            logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                     message, msgID);
          }
        }
      }
      finally
      {
        rootContainer.close();
        // Sync the environment to disk.
        msgID = MSGID_JEB_IMPORT_CLOSING_DATABASE;
        message = getMessage(msgID);
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                 message, msgID);
        tempDir.delete();
      }
    }
    finally
@@ -351,8 +311,6 @@
   */
  public void merge()
  {
    Map<AttributeType,IndexConfig>
         indexConfigs = config.getIndexConfigMap();
    ArrayList<IndexMergeThread> mergers = new ArrayList<IndexMergeThread>();
    // Create merge threads for each base DN.
@@ -362,73 +320,77 @@
      EntryContainer entryContainer = importContext.getEntryContainer();
      // For each configured attribute index.
      for (IndexConfig indexConfig : indexConfigs.values())
      for (AttributeIndex attrIndex : entryContainer.getAttributeIndexes())
      {
        AttributeIndex attrIndex =
             entryContainer.getAttributeIndex(indexConfig.getAttributeType());
        if (indexConfig.isEqualityIndex())
        int indexEntryLimit = config.getBackendIndexEntryLimit();
        if(attrIndex.getConfiguration().getIndexEntryLimit() != null)
        {
          indexEntryLimit = attrIndex.getConfiguration().getIndexEntryLimit();
        }
        if (attrIndex.equalityIndex != null)
        {
          Index index = attrIndex.equalityIndex;
          String name = containerName + "_" + index.toString();
          IndexMergeThread indexMergeThread =
               new IndexMergeThread(name, config, ldifImportConfig, index,
                                    indexConfig.getEqualityEntryLimit());
              new IndexMergeThread(config,
                                   ldifImportConfig, index,
                                   indexEntryLimit);
          mergers.add(indexMergeThread);
        }
        if (indexConfig.isPresenceIndex())
        if (attrIndex.presenceIndex != null)
        {
          Index index = attrIndex.presenceIndex;
          String name = containerName + "_" + index.toString();
          IndexMergeThread indexMergeThread =
               new IndexMergeThread(name, config, ldifImportConfig, index,
                                    indexConfig.getPresenceEntryLimit());
              new IndexMergeThread(config,
                                   ldifImportConfig, index,
                                   indexEntryLimit);
          mergers.add(indexMergeThread);
        }
        if (indexConfig.isSubstringIndex())
        if (attrIndex.substringIndex != null)
        {
          Index index = attrIndex.substringIndex;
          String name = containerName + "_" + index.toString();
          IndexMergeThread indexMergeThread =
               new IndexMergeThread(name, config, ldifImportConfig, index,
                                    indexConfig.getSubstringEntryLimit());
              new IndexMergeThread(config,
                                   ldifImportConfig, index,
                                   indexEntryLimit);
          mergers.add(indexMergeThread);
        }
        if (indexConfig.isOrderingIndex())
        if (attrIndex.orderingIndex != null)
        {
          Index index = attrIndex.orderingIndex;
          String name = containerName + "_" + index.toString();
          IndexMergeThread indexMergeThread =
               new IndexMergeThread(name, config, ldifImportConfig, index,
                                    indexConfig.getEqualityEntryLimit());
              new IndexMergeThread(config,
                                   ldifImportConfig, index,
                                   indexEntryLimit);
          mergers.add(indexMergeThread);
        }
        if (indexConfig.isApproximateIndex())
        if (attrIndex.approximateIndex != null)
        {
          Index index = attrIndex.approximateIndex;
          String name = containerName + "_" + index.toString();
          IndexMergeThread indexMergeThread =
              new IndexMergeThread(name, config, ldifImportConfig, index,
                                   indexConfig.getEqualityEntryLimit());
              new IndexMergeThread(config,
                                   ldifImportConfig, index,
                                   indexEntryLimit);
          mergers.add(indexMergeThread);
        }
      }
      // Id2Children index.
      Index id2Children = entryContainer.getID2Children();
      String name = containerName + "_" + id2Children.toString();
      IndexMergeThread indexMergeThread =
           new IndexMergeThread(name, config, ldifImportConfig,
                                id2Children,
                                config.getBackendIndexEntryLimit());
          new IndexMergeThread(config,
                               ldifImportConfig,
                               id2Children,
                               config.getBackendIndexEntryLimit());
      mergers.add(indexMergeThread);
      // Id2Subtree index.
      Index id2Subtree = entryContainer.getID2Subtree();
      name = containerName + "_" + id2Subtree.toString();
      indexMergeThread =
           new IndexMergeThread(name, config, ldifImportConfig,
                           id2Subtree,
                           config.getBackendIndexEntryLimit());
          new IndexMergeThread(config,
                               ldifImportConfig,
                               id2Subtree,
                               config.getBackendIndexEntryLimit());
      mergers.add(indexMergeThread);
    }
@@ -472,15 +434,12 @@
   *                       reading, or while reading from the LDIF file.
   */
  private boolean processLDIF()
          throws JebException, DatabaseException, IOException
      throws JebException, DatabaseException, IOException
  {
    boolean moreData = false;
    ArrayList<ImportThread> threads;
    // Create one set of worker threads for each base DN.
    int importThreadCount = config.getImportThreadCount();
    threads = new ArrayList<ImportThread>(importThreadCount*importMap.size());
    int importThreadCount = config.getBackendImportThreadCount();
    for (ImportContext ic : importMap.values())
    {
      for (int i = 0; i < importThreadCount; i++)
@@ -498,7 +457,7 @@
      // Create a counter to use to determine whether we've hit the import
      // pass size.
      int entriesProcessed = 0;
      int importPassSize   = config.getImportPassSize();
      int importPassSize   = config.getBackendImportPassSize();
      if (importPassSize <= 0)
      {
        importPassSize = Integer.MAX_VALUE;
@@ -514,6 +473,13 @@
      {
        do
        {
          if(threads.size() <= 0)
          {
            int msgID = MSGID_JEB_IMPORT_NO_WORKER_THREADS;
            String msg = getMessage(msgID);
            throw new JebException(msgID, msg);
          }
          try
          {
            // Read the next entry.
@@ -553,17 +519,20 @@
          }
        } while (true);
        // Wait for the queues to be drained.
        for (ImportContext ic : importMap.values())
        if(threads.size() > 0)
        {
          while (ic.getQueue().size() > 0)
          // Wait for the queues to be drained.
          for (ImportContext ic : importMap.values())
          {
            try
            while (ic.getQueue().size() > 0)
            {
              Thread.sleep(100);
            } catch (Exception e)
            {
              // No action needed.
              try
              {
                Thread.sleep(100);
              } catch (Exception e)
              {
                // No action needed.
              }
            }
          }
        }
@@ -612,7 +581,7 @@
   * @throws JebException If an error occurs in the JE backend.
   */
  public void processEntry(ImportContext importContext, Entry entry)
       throws JebException, DatabaseException
      throws JebException, DatabaseException
  {
    DN entryDN = entry.getDN();
    LDIFImportConfig ldifImportConfig = importContext.getLDIFImportConfig();
@@ -634,7 +603,7 @@
      {
        // See if we are allowed to replace the entry that exists.
        if (ldifImportConfig.appendToExistingData() &&
             ldifImportConfig.replaceExistingEntries())
            ldifImportConfig.replaceExistingEntries())
        {
          // Read the existing entry contents.
          Entry oldEntry = id2entry.get(txn, entryID);
@@ -672,7 +641,7 @@
        // Make sure the parent entry exists, unless this entry is a base DN.
        EntryID parentID = null;
        DN parentDN = importContext.getEntryContainer().
             getParentWithinBase(entryDN);
            getParentWithinBase(entryDN);
        if (parentDN != null)
        {
          parentID = dn2id.get(txn, parentDN);
@@ -725,7 +694,15 @@
        // Put the entry on the queue.
        try
        {
          importContext.getQueue().put(entry);
          while(!importContext.getQueue().offer(entry, 1000,
                                                TimeUnit.MILLISECONDS))
          {
            if(threads.size() <= 0)
            {
              // All worker threads died. We must stop now.
              return;
            }
          }
        }
        catch (InterruptedException e)
        {
@@ -775,7 +752,12 @@
   */
  public void uncaughtException(Thread t, Throwable e)
  {
    e.printStackTrace();
    threads.remove(t);
    int msgID = MSGID_JEB_IMPORT_THREAD_EXCEPTION;
    String msg = getMessage(msgID, t.getName(),
                            StaticUtils.stackTraceToSingleLineString(e));
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_ERROR, msg,
             msgID);
  }
  /**
@@ -883,7 +865,7 @@
        EnvironmentStats envStats =
            rootContainer.getEnvironmentStats(new StatsConfig());
        long nCacheMiss =
             envStats.getNCacheMiss() - prevEnvStats.getNCacheMiss();
            envStats.getNCacheMiss() - prevEnvStats.getNCacheMiss();
        float cacheMissRate = 0;
        if (deltaCount > 0)
opends/src/server/org/opends/server/backends/jeb/ImportThread.java
@@ -30,18 +30,13 @@
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.api.DirectoryThread;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.DatabaseException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.Map;
import java.util.ArrayList;
import java.io.IOException;
/**
 * A thread to process import entries from a queue.  Multiple instances of
@@ -146,33 +141,30 @@
  {
    Entry entry;
    Map<AttributeType,IndexConfig>
         indexConfigs = importContext.getConfig().getIndexConfigMap();
    // Figure out how many indexes there will be.
    int nIndexes = 0;
    nIndexes += 2; // For id2children and id2subtree.
    for (IndexConfig indexConfig : indexConfigs.values())
    for (AttributeIndex attrIndex : entryContainer.getAttributeIndexes())
    {
      if (indexConfig.isEqualityIndex())
      if (attrIndex.equalityIndex != null)
      {
        nIndexes++;
      }
      if (indexConfig.isPresenceIndex())
      if (attrIndex.presenceIndex != null)
      {
        nIndexes++;
      }
      if (indexConfig.isSubstringIndex())
      if (attrIndex.substringIndex != null)
      {
        nIndexes++;
      }
      if (indexConfig.isOrderingIndex())
      if (attrIndex.orderingIndex != null)
      {
        nIndexes++;
      }
      if (indexConfig.isApproximateIndex())
      if (attrIndex.approximateIndex != null)
      {
        nIndexes++;
      }
@@ -183,50 +175,55 @@
    long indexBufferSize = importContext.getBufferSize() / nIndexes;
    // Create an index builder for each attribute index database.
    for (IndexConfig indexConfig : indexConfigs.values())
    for (AttributeIndex attrIndex : entryContainer.getAttributeIndexes())
    {
      AttributeIndex attrIndex =
           entryContainer.getAttributeIndex(indexConfig.getAttributeType());
      if (indexConfig.isEqualityIndex())
      int indexEntryLimit =
          importContext.getConfig().getBackendIndexEntryLimit();
      if(attrIndex.getConfiguration().getIndexEntryLimit() != null)
      {
        indexEntryLimit = attrIndex.getConfiguration().getIndexEntryLimit();
      }
      if (attrIndex.equalityIndex != null)
      {
        IndexBuilder indexBuilder =
             new IndexBuilder(importContext,
                              attrIndex.equalityIndex,
                              indexConfig.getEqualityEntryLimit(),
                              indexEntryLimit,
                              indexBufferSize);
        builders.add(indexBuilder);
      }
      if (indexConfig.isPresenceIndex())
      if (attrIndex.presenceIndex != null)
      {
        IndexBuilder indexBuilder =
             new IndexBuilder(importContext,
                              attrIndex.presenceIndex,
                              indexConfig.getPresenceEntryLimit(),
                              indexEntryLimit,
                              indexBufferSize);
        builders.add(indexBuilder);
      }
      if (indexConfig.isSubstringIndex())
      if (attrIndex.substringIndex != null)
      {
        IndexBuilder indexBuilder =
             new IndexBuilder(importContext,
                              attrIndex.substringIndex,
                              indexConfig.getSubstringEntryLimit(),
                              indexEntryLimit,
                              indexBufferSize);
        builders.add(indexBuilder);
      }
      if (indexConfig.isOrderingIndex())
      if (attrIndex.orderingIndex != null)
      {
        IndexBuilder indexBuilder =
             new IndexBuilder(importContext, attrIndex.orderingIndex,
                              indexConfig.getEqualityEntryLimit(),
                              indexEntryLimit,
                              indexBufferSize);
        builders.add(indexBuilder);
      }
      if (indexConfig.isApproximateIndex())
      if (attrIndex.approximateIndex != null)
      {
        IndexBuilder indexBuilder =
            new IndexBuilder(importContext, attrIndex.approximateIndex,
                             indexConfig.getEqualityEntryLimit(),
                             indexEntryLimit,
                             indexBufferSize);
        builders.add(indexBuilder);
      }
@@ -317,26 +314,14 @@
      // Increment the entry count.
      importContext.incrEntryInsertCount(entryInsertCount);
    }
    catch (DatabaseException e)
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    catch (DirectoryException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    catch (IOException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new RuntimeException(e);
    }
  }
}
opends/src/server/org/opends/server/backends/jeb/Index.java
@@ -28,23 +28,14 @@
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.loggers.ErrorLogger;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.*;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.*;
import org.opends.server.util.StaticUtils;
import static org.opends.server.messages.JebMessages.*;
import java.util.*;
@@ -54,36 +45,13 @@
 * normalized form of an attribute value (or fragment of a value) appearing
 * in the entry.
 */
public class Index
public class Index extends DatabaseContainer
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The database entryContainer holding this index database.
   */
  private EntryContainer entryContainer;
  /**
   * The JE database configuration.
   */
  private DatabaseConfig dbConfig;
  /**
   * The name of the database within the entryContainer.
   */
  private String name;
  /**
   * A cached per-thread JE database handle.
   */
  private ThreadLocal<Database> threadLocalDatabase =
       new ThreadLocal<Database>();
  /**
   * The indexer object to construct index keys from LDAP attribute values.
   */
@@ -111,65 +79,95 @@
   */
  private int entryLimitExceededCount;
  private State state;
  /**
   * A flag to indicate if this index should be trusted to be consistent
   * with the entries database. If not trusted, we assume that existing
   * entryIDSets for a key is still accurate. However, keys that do not
   * exist are undefined instead of an empty entryIDSet. The following
   * rules will be observed when the index is not trusted:
   *
   * - no entryIDs will be added to a non-existing key.
   * - undefined entryIdSet will be returned whenever a key is not found.
   */
  private boolean trusted = false;
  /**
   * A flag to indicate if a rebuild process is running on this index.
   * During the rebuild process, we assume that no entryIDSets are
   * accurate and return an undefined set on all read operations.
   * However all write opeations will succeed. The rebuildRunning
   * flag overrides all behaviours of the trusted flag.
   */
  private boolean rebuildRunning = false;
  /**
   * Create a new index object.
   * @param entryContainer The database entryContainer holding this index.
   * @param name The name of the index database within the entryContainer.
   * @param indexer The indexer object to construct index keys from LDAP
   * attribute values.
   * @param state The state database to persist index state info.
   * @param indexEntryLimit The configured limit on the number of entry IDs
   * that may be indexed by one key.
   * @param cursorEntryLimit The configured limit on the number of entry IDs
   * that may be retrieved by cursoring through an index.
   * @param env The JE Environemnt
   * @param entryContainer The database entryContainer holding this index.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public Index(EntryContainer entryContainer, String name, Indexer indexer,
               int indexEntryLimit, int cursorEntryLimit)
  public Index(String name, Indexer indexer, State state,
        int indexEntryLimit, int cursorEntryLimit, Environment env,
        EntryContainer entryContainer)
      throws DatabaseException
  {
    this.entryContainer = entryContainer;
    this.name = name;
    super(name, env, entryContainer);
    this.indexer = indexer;
    this.comparator = indexer.getComparator();
    this.indexEntryLimit = indexEntryLimit;
    this.cursorEntryLimit = cursorEntryLimit;
  }
  /**
   * Open this index database.
   * @param dbConfig The requested JE database configuration.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void open(DatabaseConfig dbConfig) throws DatabaseException
  {
    this.dbConfig = dbConfig.cloneConfig();
    DatabaseConfig dbNodupsConfig = new DatabaseConfig();
    if(env.getConfig().getReadOnly())
    {
      dbNodupsConfig.setReadOnly(true);
      dbNodupsConfig.setAllowCreate(false);
      dbNodupsConfig.setTransactional(false);
    }
    else if(!env.getConfig().getTransactional())
    {
      dbNodupsConfig.setAllowCreate(true);
      dbNodupsConfig.setTransactional(false);
      dbNodupsConfig.setDeferredWrite(true);
    }
    else
    {
      dbNodupsConfig.setAllowCreate(true);
      dbNodupsConfig.setTransactional(true);
    }
    this.dbConfig = dbNodupsConfig;
    this.dbConfig.setOverrideBtreeComparator(true);
    this.dbConfig.setBtreeComparator(comparator.getClass());
    getDatabase();
  }
  /**
   * Get a handle to the database. It returns a per-thread handle to avoid
   * any thread contention on the database handle. The entryContainer is
   * responsible for closing all handles.
   *
   * @return A database handle.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public Database getDatabase() throws DatabaseException
  {
    Database database = threadLocalDatabase.get();
    if (database == null)
    this.state = state;
    this.trusted = state.getIndexTrustState(null, this);
    if(!trusted && entryContainer.getEntryCount() <= 0)
    {
      database = entryContainer.openDatabase(dbConfig, name);
      threadLocalDatabase.set(database);
      if(debugEnabled())
      {
        TRACER.debugInfo("JE Index database %s opened with %d records.",
                  database.getDatabaseName(), database.count());
      }
      // If there are no entries in the entry container then there
      // is no reason why this index can't be upgraded to trusted.
      setTrusted(null, true);
    }
    return database;
    // Issue warning if this index is not trusted
    if(!trusted)
    {
      int msgID = MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD;
      ErrorLogger.logError(ErrorLogCategory.BACKEND,
                           ErrorLogSeverity.NOTICE, msgID, name);
    }
  }
  /**
@@ -179,7 +177,7 @@
   * @param key         The index key.
   * @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 alreadly exists in the entry ID set
   *         count is exceeded. False if it already exists in the entry ID set
   *         for the given key.
   * @throws DatabaseException If an error occurs in the JE database.
   */
@@ -192,7 +190,7 @@
    DatabaseEntry data = new DatabaseEntry();
    boolean success = true;
    status = EntryContainer.read(getDatabase(), txn, key, data, lockMode);
    status = read(txn, key, data, lockMode);
    if (status == OperationStatus.SUCCESS)
    {
@@ -204,6 +202,17 @@
        {
          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, entryIDList.size(),
                             builder);
          }
        }
        else
        {
@@ -215,12 +224,15 @@
        byte[] after = entryIDList.toDatabase();
        data.setData(after);
        EntryContainer.put(getDatabase(), txn, key, data);
        put(txn, key, data);
      }
    }
    else
    {
      EntryContainer.put(getDatabase(), txn, key, entryIDData);
      if(rebuildRunning || trusted)
      {
        put(txn, key, entryIDData);
      }
    }
    return success;
@@ -241,38 +253,80 @@
    LockMode lockMode = LockMode.RMW;
    DatabaseEntry data = new DatabaseEntry();
    status = EntryContainer.read(getDatabase(), txn, key, data, lockMode);
    status = read(txn, key, data, lockMode);
    if (status == OperationStatus.SUCCESS)
    {
      EntryIDSet entryIDList = new EntryIDSet(key.getData(), data.getData());
      if (entryIDList.isDefined())
      {
        if (!entryIDList.remove(entryID))
        // Ignore failures if rebuild is running since the entry ID is
        // probably already removed.
        if (!entryIDList.remove(entryID) && !rebuildRunning)
        {
          // This shouldn't happen!
          // TODO notify the database needs to be checked
          // Invalidate the key by setting it undefined
          byte[] after = new EntryIDSet().toDatabase();
          data.setData(after);
          put(txn, key, data);
          if(trusted)
          {
            setTrusted(txn, false);
            if(debugEnabled())
            {
              StringBuilder builder = new StringBuilder();
              StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
              TRACER.debugError("The expected entry ID does not exist in " +
                  "the entry ID list for index %s.\nKey:%s",
                                name, builder.toString());
            }
            int msgID = MSGID_JEB_INDEX_CORRUPT_REQUIRES_REBUILD;
            ErrorLogger.logError(ErrorLogCategory.BACKEND,
                                 ErrorLogSeverity.SEVERE_ERROR,
                                 msgID, name);
          }
        }
        else
        {
          byte[] after = entryIDList.toDatabase();
          if (after == null)
          {
            // No more IDs, so remove the key
            EntryContainer.delete(getDatabase(), txn, key);
            // 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.
            delete(txn, key);
          }
          else
          {
            data.setData(after);
            EntryContainer.put(getDatabase(), txn, key, data);
            put(txn, key, data);
          }
        }
      }
    }
    else
    {
      // This shouldn't happen!
      // TODO notify the database needs to be checked
      // Ignore failures if rebuild is running since a empty entryIDset
      // will probably not be rebuilt.
      if(trusted && !rebuildRunning)
      {
        setTrusted(txn, false);
        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());
        }
        int msgID = MSGID_JEB_INDEX_CORRUPT_REQUIRES_REBUILD;
        ErrorLogger.logError(ErrorLogCategory.BACKEND,
                             ErrorLogSeverity.SEVERE_ERROR,
                             msgID, name);
      }
    }
  }
@@ -291,11 +345,16 @@
                                    EntryID entryID)
       throws DatabaseException
  {
    if(rebuildRunning)
    {
      return ConditionResult.UNDEFINED;
    }
    OperationStatus status;
    LockMode lockMode = LockMode.DEFAULT;
    DatabaseEntry data = new DatabaseEntry();
    status = EntryContainer.read(getDatabase(), txn, key, data, lockMode);
    status = read(txn, key, data, lockMode);
    if (status == OperationStatus.SUCCESS)
    {
      EntryIDSet entryIDList =
@@ -316,7 +375,14 @@
    }
    else
    {
      return ConditionResult.FALSE;
      if(trusted)
      {
        return ConditionResult.FALSE;
      }
      else
      {
        return ConditionResult.UNDEFINED;
      }
    }
  }
@@ -331,14 +397,26 @@
  public EntryIDSet readKey(DatabaseEntry key, Transaction txn,
                            LockMode lockMode)
  {
    if(rebuildRunning)
    {
      return new EntryIDSet();
    }
    try
    {
      OperationStatus status;
      DatabaseEntry data = new DatabaseEntry();
      status = EntryContainer.read(getDatabase(), txn, key, data, lockMode);
      status = read( txn, key, data, lockMode);
      if (status != OperationStatus.SUCCESS)
      {
        return new EntryIDSet(key.getData(), null);
        if(trusted)
        {
          return new EntryIDSet(key.getData(), null);
        }
        else
        {
          return new EntryIDSet();
        }
      }
      return new EntryIDSet(key.getData(), data.getData());
    }
@@ -369,7 +447,7 @@
    if (after == null)
    {
      // No more IDs, so remove the key.
      EntryContainer.delete(getDatabase(), txn, key);
      delete(txn, key);
    }
    else
    {
@@ -378,7 +456,7 @@
        entryLimitExceededCount++;
      }
      data.setData(after);
      EntryContainer.put(getDatabase(), txn, key, data);
      put(txn, key, data);
    }
  }
@@ -409,6 +487,13 @@
  {
    LockMode lockMode = LockMode.DEFAULT;
    // If this index is not trusted, then just return an undefined
    // id set.
    if(rebuildRunning || !trusted)
    {
      return new EntryIDSet();
    }
    try
    {
      // Total number of IDs found so far.
@@ -422,7 +507,7 @@
      OperationStatus status;
      Cursor cursor;
      cursor = getDatabase().openCursor(null, CursorConfig.READ_COMMITTED);
      cursor = openCursor(null, CursorConfig.READ_COMMITTED);
      try
      {
@@ -511,91 +596,13 @@
  }
  /**
   * Get a string representation of this object.
   * @return return A string representation of this object.
   */
  public String toString()
  {
    return name;
  }
  /**
   * Open a cursor to the JE database holding this index.
   * @param txn A JE database transaction to be associated with the cursor,
   * or null if none is required.
   * @param cursorConfig The requested JE cursor configuration.
   * @return A new JE cursor.
   * @throws DatabaseException If an error occurs while attempting to open
   * the cursor.
   */
  public Cursor openCursor(Transaction txn, CursorConfig cursorConfig)
       throws DatabaseException
  {
    return getDatabase().openCursor(txn, cursorConfig);
  }
  /**
   * Removes all records from the database.
   * @param txn A JE database transaction to be used during the clear operation
   *            or null if not required. Using transactions increases the chance
   *            of lock contention.
   * @return The number of records removed.
   * @throws DatabaseException If an error occurs while cleaning the database.
   */
  public long clear(Transaction txn) throws DatabaseException
  {
    long deletedCount = 0;
    Cursor cursor = openCursor(txn, null);
    try
    {
      if(debugEnabled())
      {
        TRACER.debugVerbose("%d existing records will be deleted from the " +
            "database", getRecordCount());
      }
      DatabaseEntry data = new DatabaseEntry();
      DatabaseEntry key = new DatabaseEntry();
      OperationStatus status;
      // Step forward until we deleted all records.
      for (status = cursor.getFirst(key, data, LockMode.DEFAULT);
           status == OperationStatus.SUCCESS;
           status = cursor.getNext(key, data, LockMode.DEFAULT))
      {
        cursor.delete();
        deletedCount++;
      }
      if(debugEnabled())
      {
        TRACER.debugVerbose("%d records deleted", deletedCount);
      }
    }
    catch(DatabaseException de)
    {
      if(debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      throw de;
    }
    finally
    {
      cursor.close();
    }
    return deletedCount;
  }
  /**
   * Update the index for a new entry.
   *
   * @param txn A database transaction, or null if none is required.
   * @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 alreadly exists for some keys.
   *         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.
   */
@@ -684,15 +691,55 @@
  }
  /**
   * Get the count of the number of entries stored.
   * Set the index entry limit.
   *
   * @return The number of entries stored.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   * @param indexEntryLimit The index entry limit to set.
   * @return True if a rebuild is required or false otherwise.
   */
  public long getRecordCount() throws DatabaseException
  public boolean setIndexEntryLimit(int indexEntryLimit)
  {
    return EntryContainer.count(getDatabase());
    boolean rebuildRequired = false;
    if(this.indexEntryLimit < indexEntryLimit &&
        entryLimitExceededCount > 0 )
    {
      rebuildRequired = true;
    }
    this.indexEntryLimit = indexEntryLimit;
    return rebuildRequired;
  }
  /**
   * Set the indexer.
   *
   * @param indexer The indexer to set
   */
  public void setIndexer(Indexer indexer)
  {
    this.indexer = indexer;
  }
  /**
   * Set the index trust state.
   * @param txn A database transaction, or null if none is required.
   * @param trusted True if this index should be trusted or false
   *                otherwise.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public synchronized void setTrusted(Transaction txn, boolean trusted)
      throws DatabaseException
  {
    this.trusted = trusted;
    state.putIndexTrustState(txn, this, trusted);
  }
  /**
   * Set the rebuild status of this index.
   * @param rebuildRunning True if a rebuild process on this index
   *                       is running or False otherwise.
   */
  public synchronized void setRebuildStatus(boolean rebuildRunning)
  {
    this.rebuildRunning = rebuildRunning;
  }
}
opends/src/server/org/opends/server/backends/jeb/IndexBuilder.java
@@ -28,6 +28,7 @@
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Entry;
import static org.opends.server.util.StaticUtils.getFileForPath;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Transaction;
@@ -139,8 +140,7 @@
    this.entryLimit = entryLimit;
    this.bufferSize = (int)bufferSize/100;
    long tid = Thread.currentThread().getId();
    fileNamePrefix = importContext.getContainerName() + "_" +
         indexer.toString() + "_" + tid + "_";
    fileNamePrefix = index.getName() + "_" + tid + "_";
    replaceExisting =
         importContext.getLDIFImportConfig().appendToExistingData() &&
         importContext.getLDIFImportConfig().replaceExistingEntries();
@@ -156,7 +156,8 @@
  public void startProcessing()
  {
    // Clean up any work files left over from a previous run.
    File tempDir = new File(importContext.getConfig().getImportTempDirectory());
    File tempDir = getFileForPath(
        importContext.getConfig().getBackendImportTempDirectory());
    File[] files = tempDir.listFiles(filter);
    if (files != null)
    {
@@ -314,7 +315,8 @@
    // Start a new file.
    fileNumber++;
    String fileName = fileNamePrefix + String.valueOf(fileNumber);
    File file = new File(importContext.getConfig().getImportTempDirectory(),
    File file = new File(getFileForPath(
        importContext.getConfig().getBackendImportTempDirectory()),
                         fileName);
    BufferedOutputStream bufferedStream =
         new BufferedOutputStream(new FileOutputStream(file));
opends/src/server/org/opends/server/backends/jeb/IndexConfig.java
File was deleted
opends/src/server/org/opends/server/backends/jeb/IndexMergeThread.java
@@ -53,6 +53,8 @@
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.messages.JebMessages.*;
import org.opends.server.admin.std.server.JEBackendCfg;
import static org.opends.server.util.StaticUtils.getFileForPath;
/**
 * A thread to merge a set of intermediate files from an index builder
@@ -74,7 +76,7 @@
  /**
   * The configuration of the JE backend containing the index.
   */
  Config config;
  JEBackendCfg config;
  /**
   * The LDIF import configuration, which indicates whether we are
@@ -123,26 +125,24 @@
  {
    public boolean accept(File d, String name)
    {
      return name.startsWith(indexName);
      return name.startsWith(index.getName());
    }
  };
  /**
   * Create a new index merge thread.
   * @param name The name of the index for use in file names and log messages.
   * @param config The configuration of the JE backend containing the index.
   * @param ldifImportConfig The LDIF import configuration, which indicates
   * whether we are appending to existing data.
   * @param index The index database to be written.
   * @param entryLimit The configured index entry limit.
   */
  IndexMergeThread(String name, Config config,
  IndexMergeThread(JEBackendCfg config,
                   LDIFImportConfig ldifImportConfig,
                   Index index, int entryLimit)
  {
    super("Index Merge Thread " + name);
    super("Index Merge Thread " + index.getName());
    this.indexName = name;
    this.config = config;
    this.ldifImportConfig = ldifImportConfig;
    this.indexer = index.indexer;
@@ -188,20 +188,20 @@
         new TreeMap<ASN1OctetString, MergeValue>(comparator);
    // Open all the files.
    File tempDir = new File(config.getImportTempDirectory());
    File tempDir = getFileForPath(config.getBackendImportTempDirectory());
    File[] files = tempDir.listFiles(filter);
    if (files == null || files.length == 0)
    {
      int msgID = MSGID_JEB_INDEX_MERGE_NO_DATA;
      String message = getMessage(msgID, indexName);
      String message = getMessage(msgID, index.getName());
      logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
               message, msgID);
      return;
    }
    int msgID = MSGID_JEB_INDEX_MERGE_START;
    String message = getMessage(msgID, files.length, indexName);
    String message = getMessage(msgID, files.length, index.getName());
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
             message, msgID);
@@ -216,7 +216,6 @@
    try
    {
      Database db = index.getDatabase();
      for (int i = 0; i < files.length; i++)
      {
@@ -249,7 +248,7 @@
            merged.clear();
            if (ldifImportConfig.appendToExistingData())
            {
              if (db.get(txn, dbKey, dbData, LockMode.RMW) ==
              if (index.read(txn, dbKey, dbData, LockMode.RMW) ==
                   OperationStatus.SUCCESS)
              {
                if (dbData.getSize() == 0)
@@ -284,7 +283,7 @@
              dbData.setData(mergedBytes);
              dbData.setSize(merged.encodedSize());
              db.put(txn, dbKey, dbData);
              index.put(txn, dbKey, dbData);
            }
            LinkedList<byte[]> arrayList = arrayMap.get(keyBytes.length);
@@ -306,6 +305,11 @@
      catch (NoSuchElementException e)
      {
      }
      if(replaceExisting)
      {
        index.setTrusted(txn, true);
      }
    }
    catch (Exception e)
    {
@@ -340,7 +344,7 @@
    }
    msgID = MSGID_JEB_INDEX_MERGE_COMPLETE;
    message = getMessage(msgID, indexName);
    message = getMessage(msgID, index.getName());
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
             message, msgID);
  }
opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java
@@ -108,7 +108,7 @@
  /**
   * The number of entries that were skipped because they were not applicable
   * for the indexType or because an error occured.
   * for the indexType or because an error occurred.
   */
  long skippedEntries = 0;
@@ -144,8 +144,7 @@
   */
  IndexRebuildThread(EntryContainer ec, Index index)
  {
    super("Index Rebuild Thread " + ec.getContainerName() + "_" +
        index.toString());
    super("Index Rebuild Thread " + index.getName());
    this.ec = ec;
    this.indexType = IndexType.INDEX;
    this.index = index;
@@ -161,8 +160,7 @@
   */
  IndexRebuildThread(EntryContainer ec, AttributeIndex index)
  {
    super("Index Rebuild Thread " + ec.getContainerName() + "_" +
        index.toString());
    super("Index Rebuild Thread " + index.getName());
    this.ec = ec;
    this.indexType = IndexType.ATTRIBUTEINDEX;
    this.attrIndex = index;
@@ -171,6 +169,74 @@
  }
  /**
   * Clear the database and prep it for the rebuild.
   *
   * @throws DatabaseException if a JE databse error occurs while clearing
   * the database being rebuilt.
   */
  public void clearDatabase() throws DatabaseException
  {
    if(indexType == null)
    {
      //TODO: throw error
      if(debugEnabled())
      {
        TRACER.debugError("No index type specified. Rebuild process " +
            "terminated.");
      }
      return;
    }
    if(indexType == IndexType.ATTRIBUTEINDEX && attrIndex == null)
    {
      //TODO: throw error
      if(debugEnabled())
      {
        TRACER.debugError("No attribute index specified. Rebuild process " +
            "terminated.");
      }
      return;
    }
    if(indexType == IndexType.INDEX && index == null)
    {
      //TODO: throw error
      if(debugEnabled())
      {
        TRACER.debugError("No index specified. Rebuild process terminated.");
      }
      return;
    }
    switch(indexType)
    {
      case DN2ID :
        ec.clearDatabase(null, ec.getDN2ID());
        break;
      case DN2URI :
        ec.clearDatabase(null, ec.getDN2URI());
        break;
      case ID2CHILDREN :
        ec.clearDatabase(null, ec.getID2Children());
        ec.getID2Children().setRebuildStatus(true);
        break;
      case ID2SUBTREE :
        ec.clearDatabase(null, ec.getID2Subtree());
        ec.getID2Subtree().setRebuildStatus(true);
        break;
      case ATTRIBUTEINDEX :
        ec.clearAttributeIndex(null, attrIndex);
        attrIndex.setRebuildStatus(true);
        break;
      case INDEX :
        ec.clearDatabase(null, index);
        index.setRebuildStatus(true);
    }
  }
  /**
   * Start the rebuild process.
   */
  public void run()
@@ -252,7 +318,6 @@
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
  }
  /**
@@ -263,17 +328,6 @@
  private void rebuildDN2ID() throws DatabaseException
  {
    DN2ID dn2id = ec.getDN2ID();
    Transaction txn = ec.beginTransaction();
    try
    {
      //Delete all records in the indexType databases.
      //TODO: Should we do a transactional delete?
      dn2id.clear(txn);
    }
    finally
    {
      EntryContainer.transactionCommit(txn);
    }
    //Iterate through the id2entry database and insert associated dn2id
    //records.
@@ -303,7 +357,7 @@
          }
          else
          {
            // The entry ID alreadly exists in the database.
            // The entry ID already exists in the database.
            // This could happen if some other process got to this entry
            // before we did. Since the backend should be offline, this
            // might be a problem.
@@ -351,17 +405,6 @@
  private void rebuildDN2URI() throws DatabaseException
  {
    DN2URI dn2uri = ec.getDN2URI();
    Transaction txn = ec.beginTransaction();
    try
    {
      //Delete all records in the indexType databases.
      //TODO: Should we do a transactional delete?
      dn2uri.clear(txn);
    }
    finally
    {
      EntryContainer.transactionCommit(txn);
    }
    //Iterate through the id2entry database and insert associated dn2uri
    //records.
@@ -392,7 +435,7 @@
          }
          else
          {
            // The entry DN and URIs alreadly exists in the database.
            // The entry DN and URIs already exists in the database.
            // This could happen if some other process got to this entry
            // before we did. Since the backend should be offline, this
            // might be a problem.
@@ -441,17 +484,6 @@
  private void rebuildID2Children() throws DatabaseException
  {
    Index id2children = ec.getID2Children();
    Transaction txn = ec.beginTransaction();
    try
    {
      //Delete all records in the indexType databases.
      //TODO: Should we do a transactional delete?
      id2children.clear(txn);
    }
    finally
    {
      EntryContainer.transactionCommit(txn);
    }
    DN2ID dn2id = ec.getDN2ID();
    DN2URI dn2uri = ec.getDN2URI();
@@ -494,7 +526,7 @@
              }
              else
              {
                // The entry alreadly exists in the database.
                // The entry already exists in the database.
                // This could happen if some other process got to this entry
                // before we did. Since the backend should be offline, this
                // might be a problem.
@@ -538,6 +570,8 @@
          }
        }
      }
      id2children.setRebuildStatus(false);
      id2children.setTrusted(null, true);
    }
    finally
    {
@@ -554,17 +588,6 @@
  private void rebuildID2Subtree() throws DatabaseException
  {
    Index id2subtree = ec.getID2Subtree();
    Transaction txn = ec.beginTransaction();
    try
    {
      //Delete all records in the indexType databases.
      //TODO: Should we do a transactional delete?
      id2subtree.clear(txn);
    }
    finally
    {
      EntryContainer.transactionCommit(txn);
    }
    DN2ID dn2id = ec.getDN2ID();
    DN2URI dn2uri = ec.getDN2URI();
@@ -645,7 +668,7 @@
            }
            else
            {
              // The entry alreadly exists in the database.
              // The entry already exists in the database.
              // This could happen if some other process got to this entry
              // before we did. Since the backend should be offline, this
              // might be a problem.
@@ -681,6 +704,8 @@
          }
        }
      }
      id2subtree.setRebuildStatus(false);
      id2subtree.setTrusted(null, true);
    }
    finally
    {
@@ -697,17 +722,6 @@
  private void rebuildAttributeIndex(AttributeIndex index)
      throws DatabaseException
  {
    Transaction txn = ec.beginTransaction();
    try
    {
      //Delete all records in the indexType databases.
      //TODO: Should we do a transactional delete?
      index.clear(txn);
    }
    finally
    {
      EntryContainer.transactionCommit(txn);
    }
    //Iterate through the id2entry database and insert associated indexType
    //records.
@@ -735,7 +749,7 @@
          }
          else
          {
            // The entry alreadly exists in one or more entry sets.
            // The entry already exists in one or more entry sets.
            // This could happen if some other process got to this entry
            // before we did. Since the backend should be offline, this
            // might be a problem.
@@ -767,6 +781,8 @@
          }
        }
      }
      index.setRebuildStatus(false);
      index.setTrusted(null, true);
    }
    finally
    {
@@ -783,17 +799,6 @@
  private void rebuildAttributeIndex(Index index)
      throws DatabaseException
  {
    Transaction txn = ec.beginTransaction();
    try
    {
      //Delete all records in the indexType databases.
      //TODO: Should we do a transactional delete?
      index.clear(txn);
    }
    finally
    {
      EntryContainer.transactionCommit(txn);
    }
    //Iterate through the id2entry database and insert associated indexType
    //records.
@@ -821,7 +826,7 @@
          }
          else
          {
            // The entry alreadly exists in one or more entry sets.
            // The entry already exists in one or more entry sets.
            // This could happen if some other process got to this entry
            // before we did. Since the backend should be offline, this
            // might be a problem.
@@ -853,6 +858,8 @@
          }
        }
      }
      index.setRebuildStatus(false);
      index.setTrusted(null, true);
    }
    finally
    {
@@ -911,7 +918,7 @@
  /**
   * Get the number of entries skipped because they were either not applicable
   * or an error occured during the process.
   * or an error occurred during the process.
   *
   * @return The number of entries skipped.
   */
opends/src/server/org/opends/server/backends/jeb/OrderingIndexer.java
@@ -57,10 +57,10 @@
  /**
   * The attribute index configuration for which this instance will
   * The attribute type for which this instance will
   * generate index keys.
   */
  private IndexConfig indexConfig;
  private AttributeType attributeType;
  /**
   * The attribute type ordering matching rule which is also the
@@ -71,13 +71,13 @@
  /**
   * Create a new attribute ordering indexer for the given index configuration.
   * @param indexConfig The index configuration for which an indexer is
   * @param attributeType The attribute type for which an indexer is
   * required.
   */
  public OrderingIndexer(IndexConfig indexConfig)
  public OrderingIndexer(AttributeType attributeType)
  {
    this.indexConfig = indexConfig;
    orderingRule = indexConfig.getAttributeType().getOrderingMatchingRule();
    this.attributeType = attributeType;
    this.orderingRule = attributeType.getOrderingMatchingRule();
  }
  /**
@@ -87,7 +87,7 @@
   */
  public String toString()
  {
    return indexConfig.getAttributeType().getNameOrOID() + ".ordering";
    return attributeType.getNameOrOID() + ".ordering";
  }
  /**
@@ -113,7 +113,7 @@
                       Set<ASN1OctetString> keys)
  {
    List<Attribute> attrList =
         entry.getAttribute(indexConfig.getAttributeType());
         entry.getAttribute(attributeType);
    if (attrList != null)
    {
      indexAttribute(attrList, keys);
@@ -138,11 +138,11 @@
  {
    List<Attribute> attrList;
    attrList = oldEntry.getAttribute(indexConfig.getAttributeType());
    attrList = oldEntry.getAttribute(attributeType);
    Set<ASN1OctetString> oldSet = new HashSet<ASN1OctetString>();
    indexAttribute(attrList, oldSet);
    attrList = newEntry.getAttribute(indexConfig.getAttributeType());
    attrList = newEntry.getAttribute(attributeType);
    Set<ASN1OctetString> newSet = new HashSet<ASN1OctetString>();
    indexAttribute(attrList, newSet);
@@ -181,7 +181,7 @@
       throws DatabaseException
  {
    List<Attribute> beforeList;
    beforeList = oldEntry.getAttribute(indexConfig.getAttributeType());
    beforeList = oldEntry.getAttribute(attributeType);
    // Pick out the modifications that apply to this indexed attribute
@@ -203,7 +203,7 @@
    {
      Attribute modAttr = mod.getAttribute();
      AttributeType modAttrType = modAttr.getAttributeType();
      if (modAttrType.equals(indexConfig.getAttributeType()))
      if (modAttrType.equals(attributeType))
      {
        switch (mod.getModificationType())
        {
opends/src/server/org/opends/server/backends/jeb/PresenceIndexer.java
@@ -30,6 +30,7 @@
import org.opends.server.types.Attribute;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.AttributeType;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.DatabaseException;
@@ -52,19 +53,19 @@
       new AttributeIndex.KeyComparator();
  /**
   * The attribute index configuration for which this instance will
   * The attribute type for which this instance will
   * generate index keys.
   */
  private IndexConfig indexConfig;
  private AttributeType attributeType;
  /**
   * Create a new attribute presence indexer.
   * @param indexConfig The attribute index configuration for which the indexer
   * @param attributeType The attribute type for which the indexer
   * is required.
   */
  public PresenceIndexer(IndexConfig indexConfig)
  public PresenceIndexer(AttributeType attributeType)
  {
    this.indexConfig = indexConfig;
    this.attributeType = attributeType;
  }
  /**
@@ -73,7 +74,7 @@
   */
  public String toString()
  {
    return indexConfig.getAttributeType().getNameOrOID() + ".presence";
    return attributeType.getNameOrOID() + ".presence";
  }
  /**
@@ -102,7 +103,7 @@
                       Set<ASN1OctetString> keys) throws DatabaseException
  {
    List<Attribute> attrList =
         entry.getAttribute(indexConfig.getAttributeType());
         entry.getAttribute(attributeType);
    if (attrList != null)
    {
      if (!attrList.isEmpty())
@@ -133,8 +134,8 @@
  {
    List<Attribute> beforeList, afterList;
    beforeList = oldEntry.getAttribute(indexConfig.getAttributeType());
    afterList = newEntry.getAttribute(indexConfig.getAttributeType());
    beforeList = oldEntry.getAttribute(attributeType);
    afterList = newEntry.getAttribute(attributeType);
    if (beforeList == null || beforeList.isEmpty())
    {
opends/src/server/org/opends/server/backends/jeb/RebuildJob.java
@@ -469,6 +469,27 @@
        timer.scheduleAtFixedRate(progressTask, progressInterval,
                                  progressInterval);
        entryContainer.exclusiveLock.lock();
        try
        {
          for(IndexRebuildThread thread : waitingThreads)
          {
            thread.clearDatabase();
          }
        }
        finally
        {
          if(!rebuildConfig.includesSystemIndex())
          {
            entryContainer.exclusiveLock.unlock();
          }
        }
        if(!rebuildConfig.includesSystemIndex())
        {
          entryContainer.sharedLock.lock();
        }
        try
        {
          while(!waitingThreads.isEmpty())
@@ -480,6 +501,14 @@
        finally
        {
          timer.cancel();
          if(rebuildConfig.includesSystemIndex())
          {
            entryContainer.exclusiveLock.unlock();
          }
          else
          {
            entryContainer.sharedLock.unlock();
          }
        }
        long totalProcessed = 0;
@@ -528,7 +557,7 @@
  /**
   * Dispatch a set of threads based on their dependency and ordering.
   */
  private void dispatchThreads()
  private void dispatchThreads() throws DatabaseException
  {
    for(IndexRebuildThread t : waitingThreads)
    {
opends/src/server/org/opends/server/backends/jeb/RootContainer.java
@@ -49,10 +49,17 @@
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.JebMessages.*;
import static org.opends.server.messages.ConfigMessages.
    MSGID_CONFIG_BACKEND_MODE_INVALID;
import static org.opends.server.messages.ConfigMessages.
    MSGID_CONFIG_BACKEND_INSANE_MODE;
import org.opends.server.api.Backend;
import org.opends.server.admin.std.server.JEBackendCfg;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.core.DirectoryServer;
import org.opends.server.config.ConfigException;
import static org.opends.server.util.StaticUtils.getFileForPath;
import org.opends.server.util.StaticUtils;
/**
 * Wrapper class for the JE environment. Root container holds all the entry
@@ -76,7 +83,7 @@
  /**
   * The backend configuration.
   */
  private Config config;
  private JEBackendCfg config;
  /**
   * The backend to which this entry root container belongs.
@@ -106,29 +113,63 @@
   * @param backend A reference to the JE back end that is creating this
   *                root container.
   */
  public RootContainer(Config config, Backend backend)
  public RootContainer(Backend backend, JEBackendCfg config)
  {
    this.env = null;
    this.monitor = null;
    this.entryContainers = new ConcurrentHashMap<DN, EntryContainer>();
    this.backend = backend;
    this.config = config;
    config.addJEChangeListener(this);
  }
  /**
   * Helper method to apply database directory permissions and create a new
   * JE environment.
   * Opens the root container using the JE configuration object provided.
   *
   * @param backendDirectory The environment home directory for JE.
   * @param backendPermission The file permissions for the environment home
   *                          directory.
   * @param envConfig The JE environment configuration.
   * @throws DatabaseException If an error occurs when creating the environment.
   * @throws ConfigException If an configuration error occurs while creating
   * the enviornment.
   */
  private void open(File backendDirectory,
                    FilePermission backendPermission,
                    EnvironmentConfig envConfig) throws DatabaseException
  public void open(EnvironmentConfig envConfig)
      throws DatabaseException, ConfigException
  {
    // Determine the backend database directory.
    File backendDirectory = getFileForPath(config.getBackendDirectory());
    //Make sure the directory is valid.
    if (!backendDirectory.isDirectory())
    {
      int msgID = MSGID_JEB_DIRECTORY_INVALID;
      String message = getMessage(msgID, backendDirectory.getPath());
      throw new ConfigException(MSGID_JEB_DIRECTORY_INVALID, message);
    }
    FilePermission backendPermission;
    try
    {
      backendPermission =
          FilePermission.decodeUNIXMode(config.getBackendMode());
    }
    catch(Exception e)
    {
      int msgID = MSGID_CONFIG_BACKEND_MODE_INVALID;
      String message = getMessage(msgID, config.dn().toString());
      throw new ConfigException(msgID, message);
    }
    //Make sure the mode will allow the server itself access to
    //the database
    if(!backendPermission.isOwnerWritable() ||
        !backendPermission.isOwnerReadable() ||
        !backendPermission.isOwnerExecutable())
    {
      int msgID = MSGID_CONFIG_BACKEND_INSANE_MODE;
      String message = getMessage(msgID);
      throw new ConfigException(msgID, message);
    }
    // Get the backend database backendDirectory permissions and apply
    if(FilePermission.canSetPermissions())
    {
@@ -164,79 +205,25 @@
          "config: %n%s", JEVersion.CURRENT_VERSION.toString(),
                          env.getConfig().toString());
          // Get current size of heap in bytes
    long heapSize = Runtime.getRuntime().totalMemory();
      // Get current size of heap in bytes
      long heapSize = Runtime.getRuntime().totalMemory();
    // Get maximum size of heap in bytes. The heap cannot grow beyond this size.
    // Any attempt will result in an OutOfMemoryException.
    long heapMaxSize = Runtime.getRuntime().maxMemory();
      // Get maximum size of heap in bytes. The heap cannot grow beyond this
      // size.
      // Any attempt will result in an OutOfMemoryException.
      long heapMaxSize = Runtime.getRuntime().maxMemory();
    // Get amount of free memory within the heap in bytes. This size will
      // Get amount of free memory within the heap in bytes. This size will
      // increase
    // after garbage collection and decrease as new objects are created.
    long heapFreeSize = Runtime.getRuntime().freeMemory();
      // after garbage collection and decrease as new objects are created.
      long heapFreeSize = Runtime.getRuntime().freeMemory();
      TRACER.debugInfo("Current size of heap: %d bytes", heapSize);
      TRACER.debugInfo("Max size of heap: %d bytes", heapMaxSize);
      TRACER.debugInfo("Free memory in heap: %d bytes", heapFreeSize);
    }
  }
  /**
   * Opens the root container.
   *
   * @throws DatabaseException If an error occurs when opening the container.
   */
  public void open() throws DatabaseException
  {
    open(config.getBackendDirectory(),
         config.getBackendPermission(),
         config.getEnvironmentConfig());
  }
  /**
   * Opens the root container using the configuration parameters provided. Any
   * configuration parameters provided will override the parameters in the
   * JE configuration object.
   *
   * @param backendDirectory The environment home directory for JE.
   * @param backendPermission he file permissions for the environment home
   *                          directory.
   * @param readOnly Open the container in read only mode.
   * @param allowCreate Allow creating new entries in the container.
   * @param transactional Use transactions on operations.
   * @param txnNoSync Use asynchronous transactions.
   * @param isLocking Create the environment with locking.
   * @param runCheckPointer Start the checkpointer.
   * @throws DatabaseException If an error occurs when openinng the container.
   */
  public void open(File backendDirectory,
                   FilePermission backendPermission,
                   boolean readOnly,
                   boolean allowCreate,
                   boolean transactional,
                   boolean txnNoSync,
                   boolean isLocking,
                   boolean runCheckPointer) throws DatabaseException
  {
    EnvironmentConfig envConfig;
    if(config.getEnvironmentConfig() != null)
    {
      envConfig = config.getEnvironmentConfig();
    }
    else
    {
      envConfig = new EnvironmentConfig();
    }
    envConfig.setReadOnly(readOnly);
    envConfig.setAllowCreate(allowCreate);
    envConfig.setTransactional(transactional);
    envConfig.setTxnNoSync(txnNoSync);
    envConfig.setConfigParam("je.env.isLocking", String.valueOf(isLocking));
    envConfig.setConfigParam("je.env.runCheckpointer",
                             String.valueOf(runCheckPointer));
    open(backendDirectory, backendPermission, envConfig);
    openEntryContainers(config.getBackendBaseDN());
  }
  /**
@@ -251,28 +238,22 @@
   * @return The opened entry container.
   * @throws DatabaseException If an error occurs while opening the entry
   *                           container.
   * @throws ConfigException If an configuration error occurs while opening
   *                         the entry container.
   */
  public EntryContainer openEntryContainer(DN baseDN) throws DatabaseException
  public EntryContainer openEntryContainer(DN baseDN)
      throws DatabaseException, ConfigException
  {
    EntryContainer ec = new EntryContainer(baseDN, backend, config, env, this);
    EntryContainer ec1=this.entryContainers.get(baseDN);
    //If an entry container for this baseDN is already open we don't allow
    //another to be opened.
      if (ec1 != null)
          throw new DatabaseException("Entry container for baseDN " +
                  baseDN.toString() + " already is open.");
    if(env.getConfig().getReadOnly())
    {
      ec.openReadOnly();
    }
    else if(!env.getConfig().getTransactional())
    {
      ec.openNonTransactional(true);
    }
    else
    {
      ec.open();
    }
    if (ec1 != null)
      throw new DatabaseException("Entry container for baseDN " +
          baseDN.toString() + " already is open.");
    ec.open();
    this.entryContainers.put(baseDN, ec);
    return ec;
@@ -284,8 +265,11 @@
   * @param baseDNs The base DNs of the entry containers to open.
   * @throws DatabaseException If an error occurs while opening the entry
   *                           container.
   * @throws ConfigException if a configuration error occurs while opening the
   *                         container.
   */
  public void openEntryContainers(DN[] baseDNs) throws DatabaseException
  private void openEntryContainers(Set<DN> baseDNs)
      throws DatabaseException, ConfigException
  {
    EntryID id;
    EntryID highestID = null;
@@ -311,8 +295,17 @@
   */
  public void closeEntryContainer(DN baseDN) throws DatabaseException
  {
    getEntryContainer(baseDN).close();
    entryContainers.remove(baseDN);
    EntryContainer ec = getEntryContainer(baseDN);
    ec.exclusiveLock.lock();
    try
    {
      ec.close();
      entryContainers.remove(baseDN);
    }
    finally
    {
      ec.exclusiveLock.unlock();
    }
  }
  /**
@@ -324,9 +317,19 @@
   */
  public void removeEntryContainer(DN baseDN) throws DatabaseException
  {
    getEntryContainer(baseDN).close();
    getEntryContainer(baseDN).removeContainer();
    entryContainers.remove(baseDN);
    EntryContainer ec = getEntryContainer(baseDN);
    ec.exclusiveLock.lock();
    try
    {
      ec.close();
      ec.removeContainer();
      entryContainers.remove(baseDN);
    }
    finally
    {
      ec.exclusiveLock.unlock();
    }
  }
  /**
@@ -340,7 +343,7 @@
    if(monitor == null)
    {
      String monitorName = backend.getBackendID() + " Database Environment";
      monitor = new DatabaseEnvironmentMonitor(monitorName, env);
      monitor = new DatabaseEnvironmentMonitor(monitorName, this);
    }
    return monitor;
@@ -349,18 +352,27 @@
  /**
   * Preload the database cache. There is no preload if the configured preload
   * time limit is zero.
   *
   * @param timeLimit The time limit for the preload process.
   */
  public void preload()
  public void preload(long timeLimit)
  {
    long timeLimit = config.getPreloadTimeLimit();
    if (timeLimit > 0)
    {
      // Get a list of all the databases used by the backend.
      ArrayList<Database> dbList = new ArrayList<Database>();
      ArrayList<DatabaseContainer> dbList =
          new ArrayList<DatabaseContainer>();
      for (EntryContainer ec : entryContainers.values())
      {
        ec.listDatabases(dbList);
        ec.sharedLock.lock();
        try
        {
          ec.listDatabases(dbList);
        }
        finally
        {
          ec.sharedLock.unlock();
        }
      }
      // Sort the list in order of priority.
@@ -376,7 +388,7 @@
        PreloadConfig preloadConfig = new PreloadConfig();
        preloadConfig.setLoadLNs(true);
        for (Database db : dbList)
        for (DatabaseContainer db : dbList)
        {
          // Calculate the remaining time.
          long timeRemaining = timeEnd - System.currentTimeMillis();
@@ -390,7 +402,7 @@
          if(debugEnabled())
          {
            TRACER.debugInfo("file=" + db.getDatabaseName() +
            TRACER.debugInfo("file=" + db.getName() +
                      " LNs=" + preloadStats.getNLNsLoaded());
          }
@@ -487,11 +499,14 @@
  {
    for(DN baseDN : entryContainers.keySet())
    {
      entryContainers.get(baseDN).close();
      entryContainers.remove(baseDN);
      closeEntryContainer(baseDN);
    }
    if (env != null) env.close();
    if (env != null)
    {
      env.close();
      env = null;
    }
  }
  /**
@@ -554,6 +569,38 @@
  }
  /**
   * Get the environment lock stats of the JE environment used in this
   * root container.
   *
   * @param statsConfig The configuration to use for the EnvironmentStats
   *                    object.
   * @return The environment status of the JE environment.
   * @throws DatabaseException If an error occurs while retriving the stats
   *                           object.
   */
  public LockStats getEnvironmentLockStats(StatsConfig statsConfig)
      throws DatabaseException
  {
    return env.getLockStats(statsConfig);
  }
  /**
   * Get the environment transaction stats of the JE environment used
   * in this root container.
   *
   * @param statsConfig The configuration to use for the EnvironmentStats
   *                    object.
   * @return The environment status of the JE environment.
   * @throws DatabaseException If an error occurs while retriving the stats
   *                           object.
   */
  public TransactionStats getEnvironmentTransactionStats(
      StatsConfig statsConfig) throws DatabaseException
  {
    return env.getTransactionStats(statsConfig);
  }
  /**
   * Get the environment config of the JE environment used in this root
   * container.
   *
@@ -567,6 +614,16 @@
  }
  /**
   * Get the backend configuration used by this root container.
   *
   * @return The JE backend configuration used by this root container.
   */
  public JEBackendCfg getConfiguration()
  {
    return config;
  }
  /**
   * Get the total number of entries in this root container.
   *
   * @return The number of entries in this root container
@@ -578,7 +635,15 @@
    long entryCount = 0;
    for(EntryContainer ec : this.entryContainers.values())
    {
      entryCount += ec.getEntryCount();
      ec.sharedLock.lock();
      try
      {
        entryCount += ec.getEntryCount();
      }
      finally
      {
        ec.sharedLock.unlock();
      }
    }
    return entryCount;
@@ -620,12 +685,45 @@
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
       JEBackendCfg cfg,
       List<String> unacceptableReasons)
      JEBackendCfg cfg,
      List<String> unacceptableReasons)
  {
    boolean acceptable = true;
    // This listener handles only the changes to JE properties.
    File backendDirectory = getFileForPath(cfg.getBackendDirectory());
    //Make sure the directory is valid.
    if (!backendDirectory.isDirectory())
    {
      int msgID = MSGID_JEB_DIRECTORY_INVALID;
      String message = getMessage(msgID, backendDirectory.getPath());
      unacceptableReasons.add(message);
      acceptable = false;
    }
    try
    {
      FilePermission newBackendPermission =
          FilePermission.decodeUNIXMode(cfg.getBackendMode());
      //Make sure the mode will allow the server itself access to
      //the database
      if(!newBackendPermission.isOwnerWritable() ||
          !newBackendPermission.isOwnerReadable() ||
          !newBackendPermission.isOwnerExecutable())
      {
        int msgID = MSGID_CONFIG_BACKEND_INSANE_MODE;
        String message = getMessage(msgID);
        unacceptableReasons.add(message);
        acceptable = false;
      }
    }
    catch(Exception e)
    {
      int msgID = MSGID_CONFIG_BACKEND_MODE_INVALID;
      String message = getMessage(msgID, cfg.dn().toString());
      unacceptableReasons.add(message);
      acceptable = false;
    }
    try
    {
@@ -653,50 +751,54 @@
    try
    {
      // Check if any JE non-mutable properties were changed.
      EnvironmentConfig oldEnvConfig = env.getConfig();
      EnvironmentConfig newEnvConfig =
           ConfigurableEnvironment.parseConfigEntry(cfg);
      Map paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
      for (Object o : paramsMap.values())
      if(env != null)
      {
        ConfigParam param = (ConfigParam) o;
        if (!param.isMutable())
        // Check if any JE non-mutable properties were changed.
        EnvironmentConfig oldEnvConfig = env.getConfig();
        EnvironmentConfig newEnvConfig =
            ConfigurableEnvironment.parseConfigEntry(cfg);
        Map paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
        for (Object o : paramsMap.values())
        {
          String oldValue = oldEnvConfig.getConfigParam(param.getName());
          String newValue = newEnvConfig.getConfigParam(param.getName());
          if (!oldValue.equalsIgnoreCase(newValue))
          ConfigParam param = (ConfigParam) o;
          if (!param.isMutable())
          {
            adminActionRequired = true;
            String configAttr = ConfigurableEnvironment.
                 getAttributeForProperty(param.getName());
            if (configAttr != null)
            String oldValue = oldEnvConfig.getConfigParam(param.getName());
            String newValue = newEnvConfig.getConfigParam(param.getName());
            if (!oldValue.equalsIgnoreCase(newValue))
            {
              int msgID = MSGID_JEB_CONFIG_ATTR_REQUIRES_RESTART;
              messages.add(getMessage(msgID, configAttr));
            }
            if(debugEnabled())
            {
              TRACER.debugInfo("The change to the following property will " +
                        "take effect when the backend is restarted: " +
                        param.getName());
              adminActionRequired = true;
              String configAttr = ConfigurableEnvironment.
                  getAttributeForProperty(param.getName());
              if (configAttr != null)
              {
                int msgID = MSGID_JEB_CONFIG_ATTR_REQUIRES_RESTART;
                messages.add(getMessage(msgID, configAttr));
              }
              if(debugEnabled())
              {
                TRACER.debugInfo("The change to the following property will " +
                    "take effect when the backend is restarted: " +
                    param.getName());
              }
            }
          }
        }
      }
      // This takes care of changes to the JE environment for those
      // properties that are mutable at runtime.
      env.setMutableConfig(newEnvConfig);
        // This takes care of changes to the JE environment for those
        // properties that are mutable at runtime.
        env.setMutableConfig(newEnvConfig);
      if (debugEnabled())
      {
        TRACER.debugInfo(env.getConfig().toString());
        if (debugEnabled())
        {
          TRACER.debugInfo(env.getConfig().toString());
        }
      }
      this.config = cfg;
    }
    catch (Exception e)
    {
      messages.add(e.getMessage());
      messages.add(StaticUtils.stackTraceToSingleLineString(e));
      ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
                                   adminActionRequired,
                                   messages);
opends/src/server/org/opends/server/backends/jeb/State.java
New file
@@ -0,0 +1,165 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.backends.jeb;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import org.opends.server.util.StaticUtils;
import com.sleepycat.je.*;
import java.util.Arrays;
/**
 * This class is responsible for storing the configuration state of
 * the JE backend for a particular suffix.
 */
public class State extends DatabaseContainer
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  private static final byte[] falseBytes = new byte[]{0x00};
  private static final byte[] trueBytes = new byte[]{0x01};
  /**
   * Create a new State object.
   *
   * @param name The name of the entry database.
   * @param env The JE Environment.
   * @param entryContainer The entryContainer of the entry database.
   * @throws com.sleepycat.je.DatabaseException If an error occurs in the
   * JE database.
   *
   */
  State(String name, Environment env, EntryContainer entryContainer)
      throws DatabaseException
  {
    super(name, env, entryContainer);
    DatabaseConfig dbNodupsConfig = new DatabaseConfig();
    if(env.getConfig().getReadOnly())
    {
      dbNodupsConfig.setReadOnly(true);
      dbNodupsConfig.setAllowCreate(false);
      dbNodupsConfig.setTransactional(false);
    }
    else if(!env.getConfig().getTransactional())
    {
      dbNodupsConfig.setAllowCreate(true);
      dbNodupsConfig.setTransactional(false);
      dbNodupsConfig.setDeferredWrite(true);
    }
    else
    {
      dbNodupsConfig.setAllowCreate(true);
      dbNodupsConfig.setTransactional(true);
    }
    this.dbConfig = dbNodupsConfig;
  }
  /**
   * Remove a record from the entry database.
   *
   * @param txn The database transaction or null if none.
   * @param index The index storing the trusted state info.
   * @return true if the entry was removed, false if it was not.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public boolean removeIndexTrustState(Transaction txn, Index index)
       throws DatabaseException
  {
    DatabaseEntry key =
        new DatabaseEntry(StaticUtils.getBytes(index.getName()));
    OperationStatus status = delete(txn, key);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
    }
    return true;
  }
  /**
   * Fetch index state from the database.
   * @param txn The database transaction or null if none.
   * @param index The index storing the trusted state info.
   * @return The trusted state of the index in the database.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public boolean getIndexTrustState(Transaction txn, Index index)
      throws DatabaseException
  {
    DatabaseEntry key =
        new DatabaseEntry(StaticUtils.getBytes(index.getName()));
    DatabaseEntry data = new DatabaseEntry();
    OperationStatus status;
    status = read(txn, key, data, LockMode.DEFAULT);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
    }
    byte[] bytes = data.getData();
    return Arrays.equals(bytes, trueBytes);
  }
  /**
   * Put index state to database.
   * @param txn The database transaction or null if none.
   * @param index The index storing the trusted state info.
   * @param trusted The state value to put into the database.
   * @return true if the entry was written, false if it was not.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public boolean putIndexTrustState(Transaction txn, Index index,
                                    boolean trusted)
       throws DatabaseException
  {
    DatabaseEntry key =
        new DatabaseEntry(StaticUtils.getBytes(index.getName()));
    DatabaseEntry data = new DatabaseEntry();
    if(trusted)
      data.setData(trueBytes);
    else
      data.setData(falseBytes);
    OperationStatus status;
    status = put(txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
    }
    return true;
  }
}
opends/src/server/org/opends/server/backends/jeb/SubstringIndexer.java
@@ -28,14 +28,9 @@
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import com.sleepycat.je.Transaction;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.*;
import java.util.Comparator;
import java.util.HashSet;
@@ -62,19 +57,26 @@
       new AttributeIndex.KeyComparator();
  /**
   * The attribute index configuration for which this instance will
   * The attribute type for which this instance will
   * generate index keys.
   */
  private IndexConfig indexConfig;
  private AttributeType attributeType;
  /**
   * The substring length.
   */
  private int substrLength;
  /**
   * Create a new attribute substring indexer for the given index configuration.
   * @param indexConfig The index configuration for which an indexer is
   * @param attributeType The attribute type for which an indexer is
   * required.
   * @param substringLength The decomposed substring length.
   */
  public SubstringIndexer(IndexConfig indexConfig)
  public SubstringIndexer(AttributeType attributeType, int substringLength)
  {
    this.indexConfig = indexConfig;
    this.attributeType = attributeType;
    this.substrLength = substringLength;
  }
  /**
@@ -84,7 +86,7 @@
   */
  public String toString()
  {
    return indexConfig.getAttributeType().getNameOrOID() + ".substring";
    return attributeType.getNameOrOID() + ".substring";
  }
  /**
@@ -110,7 +112,7 @@
                       Set<ASN1OctetString> keys)
  {
    List<Attribute> attrList =
         entry.getAttribute(indexConfig.getAttributeType());
         entry.getAttribute(attributeType);
    if (attrList != null)
    {
      indexAttribute(attrList, keys);
@@ -135,11 +137,11 @@
  {
    List<Attribute> attrList;
    attrList = oldEntry.getAttribute(indexConfig.getAttributeType());
    attrList = oldEntry.getAttribute(attributeType);
    Set<ASN1OctetString> oldSet = new HashSet<ASN1OctetString>();
    indexAttribute(attrList, oldSet);
    attrList = newEntry.getAttribute(indexConfig.getAttributeType());
    attrList = newEntry.getAttribute(attributeType);
    Set<ASN1OctetString> newSet = new HashSet<ASN1OctetString>();
    indexAttribute(attrList, newSet);
@@ -224,7 +226,6 @@
   */
  private void substringKeys(byte[] value, Set<ASN1OctetString> set)
  {
    int substrLength = indexConfig.getSubstringLength();
    byte[] keyBytes;
    // Example: The value is ABCDE and the substring length is 3.
opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
@@ -61,7 +61,6 @@
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.JebMessages.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -90,11 +89,6 @@
  private VerifyConfig verifyConfig;
  /**
   * The configuration of the JE backend.
   */
  private Config config;
  /**
   * The root container used for the verify job.
   */
  RootContainer rootContainer;
@@ -191,13 +185,11 @@
  /**
   * Construct a VerifyJob.
   *
   * @param config The backend configuration.
   * @param verifyConfig The verify configuration.
   */
  public VerifyJob(Config config, VerifyConfig verifyConfig)
  public VerifyJob(VerifyConfig verifyConfig)
  {
    this.verifyConfig = verifyConfig;
    this.config = config;
  }
  /**
@@ -215,209 +207,214 @@
    EntryContainer entryContainer =
        rootContainer.getEntryContainer(verifyConfig.getBaseDN());
    ArrayList<String> completeList = verifyConfig.getCompleteList();
    ArrayList<String> cleanList = verifyConfig.getCleanList();
    entryContainer.sharedLock.lock();
    try
    {
      ArrayList<String> completeList = verifyConfig.getCompleteList();
      ArrayList<String> cleanList = verifyConfig.getCleanList();
    boolean cleanMode = false;
    if (completeList.isEmpty() && cleanList.isEmpty())
    {
      verifyDN2ID = true;
      verifyID2Children = true;
      verifyID2Subtree = true;
      Map<AttributeType,IndexConfig> indexMap = config.getIndexConfigMap();
      for (IndexConfig ic : indexMap.values())
      boolean cleanMode = false;
      if (completeList.isEmpty() && cleanList.isEmpty())
      {
        AttributeIndex attrIndex =
             entryContainer.getAttributeIndex(ic.getAttributeType());
        attrIndexList.add(attrIndex);
      }
    }
    else
    {
      ArrayList<String> list;
      if (!completeList.isEmpty())
      {
        list = completeList;
        verifyDN2ID = true;
        verifyID2Children = true;
        verifyID2Subtree = true;
        attrIndexList.addAll(entryContainer.getAttributeIndexes());
      }
      else
      {
        list = cleanList;
        cleanMode = true;
      }
      for (String index : list)
      {
        String lowerName = index.toLowerCase();
        if (lowerName.equals("dn2id"))
        ArrayList<String> list;
        if (!completeList.isEmpty())
        {
          verifyDN2ID = true;
        }
        else if (lowerName.equals("id2children"))
        {
          verifyID2Children = true;
        }
        else if (lowerName.equals("id2subtree"))
        {
          verifyID2Subtree = true;
          list = completeList;
        }
        else
        {
          AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
          if (attrType == null)
          list = cleanList;
          cleanMode = true;
        }
        for (String index : list)
        {
          String lowerName = index.toLowerCase();
          if (lowerName.equals("dn2id"))
          {
            int msgID = MSGID_JEB_ATTRIBUTE_INDEX_NOT_CONFIGURED;
            String msg = getMessage(msgID, index);
            throw new JebException(msgID, msg);
            verifyDN2ID = true;
          }
          AttributeIndex attrIndex = entryContainer.getAttributeIndex(attrType);
          if (attrIndex == null)
          else if (lowerName.equals("id2children"))
          {
            int msgID = MSGID_JEB_ATTRIBUTE_INDEX_NOT_CONFIGURED;
            String msg = getMessage(msgID, index);
            throw new JebException(msgID, msg);
            verifyID2Children = true;
          }
          attrIndexList.add(attrIndex);
        }
      }
    }
    entryLimitMap =
         new IdentityHashMap<Index,HashMap<ByteString,Long>>(
              attrIndexList.size());
    // We will be updating these files independently of the indexes
    // so we need direct access to them rather than going through
    // the entry entryContainer methods.
    id2entry = entryContainer.getID2Entry();
    dn2id = entryContainer.getDN2ID();
    id2c = entryContainer.getID2Children();
    id2s = entryContainer.getID2Subtree();
    // Make a note of the time we started.
    long startTime = System.currentTimeMillis();
    // Start a timer for the progress report.
    Timer timer = new Timer();
    TimerTask progressTask = new ProgressTask();
    timer.scheduleAtFixedRate(progressTask, progressInterval,
            progressInterval);
    // Iterate through the index keys.
    try
    {
        if (cleanMode)
        {
            iterateIndex();
        }
        else
        {
            iterateID2Entry();
        }
    }
    finally
    {
        timer.cancel();
    }
    long finishTime = System.currentTimeMillis();
    long totalTime = (finishTime - startTime);
    float rate = 0;
    if (totalTime > 0)
    {
      rate = 1000f*keyCount / totalTime;
    }
    addStatEntry(statEntry, "verify-error-count",
              String.valueOf(errorCount));
    addStatEntry(statEntry, "verify-key-count",
              String.valueOf(keyCount));
    if (cleanMode)
    {
      int msgID = MSGID_JEB_VERIFY_CLEAN_FINAL_STATUS;
      String message = getMessage(msgID, keyCount, errorCount,
                                  totalTime/1000, rate);
      logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
               message, msgID);
      if (multiReferenceCount > 0)
      {
        float averageEntryReferences = 0;
        if (keyCount > 0)
        {
          averageEntryReferences = (float)entryReferencesCount/keyCount;
        }
        msgID = MSGID_JEB_VERIFY_MULTIPLE_REFERENCE_COUNT;
        message = getMessage(msgID, multiReferenceCount);
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                 message, msgID);
        addStatEntry(statEntry, "verify-multiple-reference-count",
                String.valueOf(multiReferenceCount));
        msgID = MSGID_JEB_VERIFY_ENTRY_LIMIT_EXCEEDED_COUNT;
        message = getMessage(msgID, entryLimitExceededCount);
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                 message, msgID);
        addStatEntry(statEntry, "verify-entry-limit-exceeded-count",
                String.valueOf(entryLimitExceededCount));
        msgID = MSGID_JEB_VERIFY_AVERAGE_REFERENCE_COUNT;
        message = getMessage(msgID, averageEntryReferences);
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                 message, msgID);
        addStatEntry(statEntry, "verify-average-reference-count",
                String.valueOf(averageEntryReferences));
        msgID = MSGID_JEB_VERIFY_MAX_REFERENCE_COUNT;
        message = getMessage(msgID, maxEntryPerValue);
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                 message, msgID);
        addStatEntry(statEntry, "verify-max-reference-count",
                   String.valueOf(maxEntryPerValue));
      }
    }
    else
    {
      int msgID = MSGID_JEB_VERIFY_FINAL_STATUS;
      String message = getMessage(msgID, keyCount, errorCount,
                                  totalTime/1000, rate);
      logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
               message, msgID);
      //TODO add entry-limit-stats to the statEntry
      if (entryLimitMap.size() > 0)
      {
        msgID = MSGID_JEB_VERIFY_ENTRY_LIMIT_STATS_HEADER;
        message = getMessage(msgID);
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                 message, msgID);
        for (Map.Entry<Index,HashMap<ByteString,Long>> mapEntry :
             entryLimitMap.entrySet())
        {
          Index index = mapEntry.getKey();
          Long[] values = mapEntry.getValue().values().toArray(new Long[0]);
          // Calculate the median value for entry limit exceeded.
          Arrays.sort(values);
          long medianValue;
          int x = values.length / 2;
          if (values.length % 2 == 0)
          else if (lowerName.equals("id2subtree"))
          {
            medianValue = (values[x] + values[x-1]) / 2;
            verifyID2Subtree = true;
          }
          else
          {
            medianValue = values[x];
            AttributeType attrType =
                DirectoryServer.getAttributeType(lowerName);
            if (attrType == null)
            {
              int msgID = MSGID_JEB_ATTRIBUTE_INDEX_NOT_CONFIGURED;
              String msg = getMessage(msgID, index);
              throw new JebException(msgID, msg);
            }
            AttributeIndex attrIndex =
                entryContainer.getAttributeIndex(attrType);
            if (attrIndex == null)
            {
              int msgID = MSGID_JEB_ATTRIBUTE_INDEX_NOT_CONFIGURED;
              String msg = getMessage(msgID, index);
              throw new JebException(msgID, msg);
            }
            attrIndexList.add(attrIndex);
          }
          msgID = MSGID_JEB_VERIFY_ENTRY_LIMIT_STATS_ROW;
          message = getMessage(msgID, index.toString(), values.length,
                               values[0], values[values.length-1], medianValue);
          logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                   message, msgID);
        }
      }
      entryLimitMap =
          new IdentityHashMap<Index,HashMap<ByteString,Long>>(
              attrIndexList.size());
      // We will be updating these files independently of the indexes
      // so we need direct access to them rather than going through
      // the entry entryContainer methods.
      id2entry = entryContainer.getID2Entry();
      dn2id = entryContainer.getDN2ID();
      id2c = entryContainer.getID2Children();
      id2s = entryContainer.getID2Subtree();
      // Make a note of the time we started.
      long startTime = System.currentTimeMillis();
      // Start a timer for the progress report.
      Timer timer = new Timer();
      TimerTask progressTask = new ProgressTask();
      timer.scheduleAtFixedRate(progressTask, progressInterval,
                                progressInterval);
      // Iterate through the index keys.
      try
      {
        if (cleanMode)
        {
          iterateIndex();
        }
        else
        {
          iterateID2Entry();
        }
      }
      finally
      {
        timer.cancel();
      }
      long finishTime = System.currentTimeMillis();
      long totalTime = (finishTime - startTime);
      float rate = 0;
      if (totalTime > 0)
      {
        rate = 1000f*keyCount / totalTime;
      }
      addStatEntry(statEntry, "verify-error-count",
                   String.valueOf(errorCount));
      addStatEntry(statEntry, "verify-key-count",
                   String.valueOf(keyCount));
      if (cleanMode)
      {
        int msgID = MSGID_JEB_VERIFY_CLEAN_FINAL_STATUS;
        String message = getMessage(msgID, keyCount, errorCount,
                                    totalTime/1000, rate);
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                 message, msgID);
        if (multiReferenceCount > 0)
        {
          float averageEntryReferences = 0;
          if (keyCount > 0)
          {
            averageEntryReferences = (float)entryReferencesCount/keyCount;
          }
          msgID = MSGID_JEB_VERIFY_MULTIPLE_REFERENCE_COUNT;
          message = getMessage(msgID, multiReferenceCount);
          logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                   message, msgID);
          addStatEntry(statEntry, "verify-multiple-reference-count",
                       String.valueOf(multiReferenceCount));
          msgID = MSGID_JEB_VERIFY_ENTRY_LIMIT_EXCEEDED_COUNT;
          message = getMessage(msgID, entryLimitExceededCount);
          logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                   message, msgID);
          addStatEntry(statEntry, "verify-entry-limit-exceeded-count",
                       String.valueOf(entryLimitExceededCount));
          msgID = MSGID_JEB_VERIFY_AVERAGE_REFERENCE_COUNT;
          message = getMessage(msgID, averageEntryReferences);
          logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                   message, msgID);
          addStatEntry(statEntry, "verify-average-reference-count",
                       String.valueOf(averageEntryReferences));
          msgID = MSGID_JEB_VERIFY_MAX_REFERENCE_COUNT;
          message = getMessage(msgID, maxEntryPerValue);
          logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                   message, msgID);
          addStatEntry(statEntry, "verify-max-reference-count",
                       String.valueOf(maxEntryPerValue));
        }
      }
      else
      {
        int msgID = MSGID_JEB_VERIFY_FINAL_STATUS;
        String message = getMessage(msgID, keyCount, errorCount,
                                    totalTime/1000, rate);
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                 message, msgID);
        //TODO add entry-limit-stats to the statEntry
        if (entryLimitMap.size() > 0)
        {
          msgID = MSGID_JEB_VERIFY_ENTRY_LIMIT_STATS_HEADER;
          message = getMessage(msgID);
          logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                   message, msgID);
          for (Map.Entry<Index,HashMap<ByteString,Long>> mapEntry :
              entryLimitMap.entrySet())
          {
            Index index = mapEntry.getKey();
            Long[] values = mapEntry.getValue().values().toArray(new Long[0]);
            // Calculate the median value for entry limit exceeded.
            Arrays.sort(values);
            long medianValue;
            int x = values.length / 2;
            if (values.length % 2 == 0)
            {
              medianValue = (values[x] + values[x-1]) / 2;
            }
            else
            {
              medianValue = values[x];
            }
            msgID = MSGID_JEB_VERIFY_ENTRY_LIMIT_STATS_ROW;
            message = getMessage(msgID, index.toString(), values.length,
                                 values[0], values[values.length-1],
                                 medianValue);
            logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
                     message, msgID);
          }
        }
      }
    }
    finally
    {
      entryContainer.sharedLock.unlock();
    }
  }
@@ -1482,7 +1479,7 @@
   * @param keyBytes The bytes of the key.
   * @return A string that may be logged or printed.
   */
  public String keyDump(Index index, byte[] keyBytes)
  private String keyDump(Index index, byte[] keyBytes)
  {
/*
    String str;
@@ -1549,7 +1546,7 @@
   * @param attrList The attribute to be checked.
   * @throws DirectoryException If a Directory Server error occurs.
   */
  public void verifyAttribute(AttributeIndex attrIndex, EntryID entryID,
  private void verifyAttribute(AttributeIndex attrIndex, EntryID entryID,
                              List<Attribute> attrList)
       throws DirectoryException
  {
@@ -1559,11 +1556,10 @@
    Index substringIndex = attrIndex.substringIndex;
    Index orderingIndex = attrIndex.orderingIndex;
    Index approximateIndex = attrIndex.approximateIndex;
    IndexConfig indexConfig = attrIndex.indexConfig;
    DatabaseEntry presenceKey = AttributeIndex.presenceKey;
    // Presence index.
    if (!attrList.isEmpty() && indexConfig.isPresenceIndex())
    if (!attrList.isEmpty() && presenceIndex != null)
    {
      try
      {
@@ -1608,7 +1604,7 @@
          byte[] normalizedBytes = value.getNormalizedValue().value();
          // Equality index.
          if (indexConfig.isEqualityIndex())
          if (equalityIndex != null)
          {
            DatabaseEntry key = new DatabaseEntry(normalizedBytes);
            try
@@ -1645,7 +1641,7 @@
          }
          // Substring index.
          if (indexConfig.isSubstringIndex())
          if (substringIndex != null)
          {
            Set<ByteString> keyBytesSet =
                 attrIndex.substringKeys(normalizedBytes);
@@ -1688,7 +1684,7 @@
          }
          // Ordering index.
          if (indexConfig.isOrderingIndex())
          if (orderingIndex != null)
          {
            // Use the ordering matching rule to normalize the value.
            OrderingMatchingRule orderingRule =
@@ -1731,7 +1727,7 @@
            }
          }
          // Approximate index.
          if (indexConfig.isApproximateIndex())
          if (approximateIndex != null)
          {
            // Use the approximate matching rule to normalize the value.
            ApproximateMatchingRule approximateRule =
@@ -1784,7 +1780,7 @@
   * @param dn The DN.
   * @return The parent DN or null if the given DN is a base DN.
   */
  public DN getParent(DN dn)
  private DN getParent(DN dn)
  {
    if (dn.equals(verifyConfig.getBaseDN()))
    {
opends/src/server/org/opends/server/loggers/LogPublisherErrorHandler.java
@@ -44,7 +44,7 @@
public class LogPublisherErrorHandler
{
  private DN publisherConfigDN;
  private boolean writeErrorOccured = false;
  private boolean writeErroroccurred = false;
  /**
   * Construct a new log publisher error handler for a log publisher
@@ -66,21 +66,21 @@
   */
  public void handleWriteError(String record, Throwable ex)
  {
    if(!writeErrorOccured)
    if(!writeErroroccurred)
    {
      int msgID = MSGID_LOGGER_ERROR_WRITING_RECORD;
      String msg = getMessage(msgID, publisherConfigDN.toString(),
                              stackTraceToSingleLineString(ex));
      System.err.println(msg);
      writeErrorOccured = true;
      writeErroroccurred = true;
    }
  }
  /**
   * Handle an exception which occured while trying to open a log
   * Handle an exception which occurred while trying to open a log
   * file.
   * @param file - the file which was being opened.
   * @param ex - the exception occured.
   * @param ex - the exception occurred.
   */
  public void handleOpenError(File file, Throwable ex)
  {
@@ -92,9 +92,9 @@
  }
  /**
   * Handle an exception which occured while trying to close a log
   * Handle an exception which occurred while trying to close a log
   * file.
   * @param ex - the exception occured.
   * @param ex - the exception occurred.
   */
  public void handleCloseError(Throwable ex)
  {
@@ -105,9 +105,9 @@
  }
  /**
   * Handle an exception which occured while trying to flush the
   * Handle an exception which occurred while trying to flush the
   * writer buffer.
   * @param ex - the exception occured.
   * @param ex - the exception occurred.
   */
  public void handleFlushError(Throwable ex)
  {
opends/src/server/org/opends/server/loggers/MultifileTextWriter.java
@@ -563,7 +563,7 @@
  }
  /**
   * Tries to rotate the log files. If the new log file alreadly exists, it
   * Tries to rotate the log files. If the new log file already exists, it
   * tries to rename the file. On failure, all subsequent log write requests
   * will throw exceptions.
   */
@@ -660,9 +660,9 @@
  /**
   * Retrieves the last time a log file was rotated in this instance of
   * Directory Server. If a log rotation never
   * occured, this value will be the time the server started.
   * occurred, this value will be the time the server started.
   *
   * @return The last time log rotation occured.
   * @return The last time log rotation occurred.
   */
  public long getLastRotationTime()
  {
@@ -670,7 +670,7 @@
  }
  /**
   * Retrieves the total number file rotations occured in this instance of the
   * Retrieves the total number file rotations occurred in this instance of the
   * Directory Server.
   *
   * @return The total number of file rotations.
opends/src/server/org/opends/server/loggers/TimeLimitRotationPolicy.java
@@ -103,7 +103,7 @@
    if (debugEnabled())
    {
      TRACER.debugInfo("Last rotation occured %ds ago. " +
      TRACER.debugInfo("Last rotation occurred %ds ago. " +
          "Next rotation in %ds", currInterval / 1000,
                                   (timeInterval - currInterval)/1000);
    }
opends/src/server/org/opends/server/messages/BackendMessages.java
@@ -3705,7 +3705,7 @@
    registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_NOT_RESTORED,
                    "An error occrred while attempting to write new " +
                    "versions of the server schema files:  %s.  A problem " +
                    "also occured when attempting to restore the original " +
                    "also occurred when attempting to restore the original " +
                    "schema configuration, so the server may be left in an " +
                    "inconsistent state and could require manual cleanup");
    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_ATTRIBUTE_TYPE,
opends/src/server/org/opends/server/messages/ConfigMessages.java
@@ -9550,7 +9550,7 @@
                    "Directory Server log retention policy from the " +
                    "information in configuration entry %s:  %s");
    registerMessage(MSGID_CONFIG_LOGGING_CANNOT_CREATE_WRITER,
                    "An error occured while attempting create a text writer " +
                    "An error occurred while attempting create a text writer " +
                    "for a Directory Server logger from the information " +
                    "in configuration entry %s:  %s");
opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -8314,7 +8314,7 @@
                    "An error occurred while trying to load class %s to use " +
                    "as the Directory Server work queue implementation:  %s");
    registerMessage(MSGID_WORKQ_CANNOT_INSTANTIATE,
                    "An error occured while trying to create an instance " +
                    "An error occurred while trying to create an instance " +
                    "of class %s to use as the Directory Server work queue:  " +
                    "%s");
    registerMessage(MSGID_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS,
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -3964,7 +3964,7 @@
  /**
   * The message ID for the message that will be used if SASL DIGEST-MD5
   * authentication fails because an error occured while trying to get the
   * authentication fails because an error occurred while trying to get the
   * clear-text password value(s) from a user's entry.
   */
  public static final int MSGID_SASLDIGESTMD5_CANNOT_GET_REVERSIBLE_PASSWORDS =
@@ -3974,7 +3974,7 @@
  /**
   * The message ID for the message that will be used if SASL CRAM-MD5
   * authentication fails because an error occured while trying to get the
   * authentication fails because an error occurred while trying to get the
   * clear-text password value(s) from a user's entry.
   */
  public static final int MSGID_SASLCRAMMD5_CANNOT_GET_REVERSIBLE_PASSWORDS =
@@ -4046,7 +4046,7 @@
   * The message ID for the message that will be used if an error occurs while
   * attempting to retrieve an entry as a potential member of the static group.
   * This takes three arguments, which are the DN of the target entry, the DN of
   * the static group entry, and a message explaining the problem that occured.
   * the static group entry, and a message explaining the problem that occurred.
   */
  public static final int MSGID_STATICMEMBERS_CANNOT_GET_ENTRY =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 384;
@@ -7465,11 +7465,11 @@
                    "Unable to retrieve an existing cache entry from the " +
                    "file system entry cache");
    registerMessage(MSGID_FSCACHE_CANNOT_SET_JE_MEMORY_PCT,
                    "Internal error occured while trying to set the entry " +
                    "Internal error occurred while trying to set the entry " +
                    "cache backend internal cache size as percentage. The " +
                    "previous or default value will be used instead");
    registerMessage(MSGID_FSCACHE_CANNOT_SET_JE_MEMORY_SIZE,
                    "Internal error occured while trying to set the entry " +
                    "Internal error occurred while trying to set the entry " +
                    "cache backend internal cache size in bytes. The " +
                    "previous or default value will be used instead");
    registerMessage(MSGID_FSCACHE_OFFLINE_STATE_FAIL,
opends/src/server/org/opends/server/messages/JebMessages.java
@@ -1054,7 +1054,7 @@
       CATEGORY_MASK_JEB | SEVERITY_MASK_INFORMATIONAL | 133;
  /**
   * The message ID used to indicate that an error occured in an index rebuild
   * The message ID used to indicate that an error occurred in an index rebuild
   * thread and it is terminated. This message takes two arguments: the name
   * of the index rebuild thread that failed and the exception that caused
   * the failure.
@@ -1063,7 +1063,7 @@
      CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 134;
  /**
   * The message ID used to indicate that an error occured while inserting
   * The message ID used to indicate that an error occurred while inserting
   * an entry into a database/index during the rebuild process. This message
   * takes two arguments: the name of the database/index being inserted into,
   * and the exception that caused the failure.
@@ -1157,6 +1157,56 @@
  public static final int MSGID_JEB_UNABLE_SET_PERMISSIONS =
      CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_WARNING | 146;
   /**
   * The message ID used to indicate a index entry limit has been
   * exceeded and that the index needs to be rebuilt before the
   * new entry limit is used.
   */
  public static final int MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD =
      CATEGORY_MASK_JEB | SEVERITY_MASK_NOTICE | 148;
  /**
   * The message ID used to indicate the newly created index needs
   * to be rebuilt before it will be used.
   */
  public static final int MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD =
      CATEGORY_MASK_JEB | SEVERITY_MASK_NOTICE | 150;
  /**
   * The message ID used to indicate an index is corrupt and needs
   * to be rebuilt before it will be used again.
   */
  public static final int MSGID_JEB_INDEX_CORRUPT_REQUIRES_REBUILD =
      CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 151;
  /**
   * The message ID used to indicate an import process can not start
   * while the backend is online.
   */
  public static final int MSGID_JEB_IMPORT_BACKEND_ONLINE =
      CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 152;
  /**
   * The message ID used to indicate an error has occurred during the import
   * process.
   */
  public static final int MSGID_JEB_IMPORT_THREAD_EXCEPTION =
      CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 153;
  /**
   * The message ID used to indicate an error where there are no more worker
   * threads to process the imported entries.
   */
  public static final int MSGID_JEB_IMPORT_NO_WORKER_THREADS =
      CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 154;
  /**
   * The message ID used to indicate an error where the temp import directory
   * can not be created.
   */
  public static final int MSGID_JEB_IMPORT_CREATE_TMPDIR_ERROR =
      CATEGORY_MASK_JEB | SEVERITY_MASK_SEVERE_ERROR | 155;
  /**
   * Associates a set of generic messages with the message IDs defined in this
@@ -1441,7 +1491,7 @@
                    "This search operation has checked the maximum of %d " +
                    "entries for matches");
    registerMessage(MSGID_JEB_SET_PERMISSIONS_FAILED,
                    "An error occured while setting file permissions for " +
                    "An error occurred while setting file permissions for " +
                    "the backend database directory %s: %s");
    registerMessage(MSGID_JEB_GET_ENTRY_COUNT_FAILED,
                    "Unable to determine the total number of entries in the " +
@@ -1458,9 +1508,9 @@
                    "Rebuild complete. Processed %d records in %d seconds " +
                    "(average rate %.1f/sec)");
    registerMessage(MSGID_JEB_REBUILD_INDEX_FAILED,
                    "An error occured while rebuilding index %s: %s");
                    "An error occurred while rebuilding index %s: %s");
    registerMessage(MSGID_JEB_REBUILD_INSERT_ENTRY_FAILED,
                    "An error occured while inserting entry into the %s " +
                    "An error occurred while inserting entry into the %s " +
                    "database/index: %s");
    registerMessage(MSGID_JEB_REBUILD_INDEX_CONFLICT,
                    "Another rebuild of index %s is already in progress");
@@ -1488,5 +1538,28 @@
    registerMessage(MSGID_JEB_UNABLE_SET_PERMISSIONS,
                    "This platform does not support setting file " +
                    "permissions %s to the database directory %s");
    registerMessage(MSGID_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD,
                    "Some index keys have already exceeded the previous " +
                    "index entry limit in index %s. This index must be " +
                    "rebuilt before it can use the new limit");
    registerMessage(MSGID_JEB_INDEX_ADD_REQUIRES_REBUILD,
                    "Index %s is currently operating in a degraded read-only " +
                    "state and must be rebuilt before it can used");
    registerMessage(MSGID_JEB_INDEX_CORRUPT_REQUIRES_REBUILD,
                    "An error occurred while reading from index %s. The " +
                    "index seems to be corrupt and is now operating in " +
                    "a degraded read-only state. The index must be rebuilt " +
                    "before it can return to normal operation");
    registerMessage(MSGID_JEB_IMPORT_BACKEND_ONLINE,
                    "The backend must be disabled before the import process" +
                    "can start");
    registerMessage(MSGID_JEB_IMPORT_THREAD_EXCEPTION,
                    "An error occurred in import thread %s: %s. The thread " +
                    "can not continue");
    registerMessage(MSGID_JEB_IMPORT_NO_WORKER_THREADS,
                    "There are no more import worker threads to process the " +
                    "imported entries");
    registerMessage(MSGID_JEB_IMPORT_CREATE_TMPDIR_ERROR,
                    "Unable to create the temporary directory %s");
  }
}
opends/src/server/org/opends/server/messages/LoggerMessages.java
@@ -39,7 +39,7 @@
public class LoggerMessages
{
  /**
   * The message ID for the message that will be used if an error occured
   * The message ID for the message that will be used if an error occurred
   * while writing a log record.  This takes a two arguments, which
   * are the logger that encountered the error and  a string
   * representation of the exception that was caught.
@@ -50,7 +50,7 @@
  /**
   * The message ID for the message that will be used if an error occured
   * The message ID for the message that will be used if an error occurred
   * while opening a log file.  This takes a two arguments, which
   * are the logger that encountered the error and  a string
   * representation of the exception that was caught.
@@ -61,7 +61,7 @@
  /**
   * The message ID for the message that will be used if an error occured
   * The message ID for the message that will be used if an error occurred
   * while closing a log file.  This takes a two arguments, which
   * are the logger that encountered the error and  a string
   * representation of the exception that was caught.
@@ -72,7 +72,7 @@
  /**
   * The message ID for the message that will be used if an error occured
   * The message ID for the message that will be used if an error occurred
   * while flushing the writer buffer.  This takes a two arguments, which
   * are the logger that encountered the error and  a string
   * representation of the exception that was caught.
@@ -112,7 +112,7 @@
  /**
   * The message ID for the message that will be used if an error occured
   * The message ID for the message that will be used if an error occurred
   * while setting file permissions on a log file. This takes the name of the
   * file as the argument.
   */
@@ -138,15 +138,15 @@
  public static void registerMessages()
  {
    registerMessage(MSGID_LOGGER_ERROR_WRITING_RECORD,
                    "Error occured while writing log record for logger " +
                    "Error occurred while writing log record for logger " +
                    "%s: %s. Any further write errors will be ignored");
    registerMessage(MSGID_LOGGER_ERROR_OPENING_FILE,
                    "Error occured while opening log file %s for logger %s: " +
                    "Error occurred while opening log file %s for logger %s: " +
                    "%s");
    registerMessage(MSGID_LOGGER_ERROR_CLOSING_FILE,
                    "Error occured while closing log file for logger %s: %s");
                    "Error occurred while closing log file for logger %s: %s");
    registerMessage(MSGID_LOGGER_ERROR_FLUSHING_BUFFER,
                    "Error occured while flushing writer buffer for " +
                    "Error occurred while flushing writer buffer for " +
                    "logger %s: %s");
    registerMessage(MSGID_ERROR_LOGGER_INVALID_SEVERITY,
                    "Invalid error log severity \"%s\"");
@@ -155,7 +155,7 @@
    registerMessage(MSGID_ERROR_LOGGER_INVALID_OVERRIDE_SEVERITY,
                    "Invalid override of severity level \"%s\"");
    registerMessage(MSGID_LOGGER_SET_PERMISSION_FAILED,
                    "Error occured while setting file permissions for the " +
                    "Error occurred while setting file permissions for the " +
                    "log file %s: %s");
    registerMessage(MSGID_LOGGER_UNABLE_SET_PERMISSIONS,
                    "This platform does not support setting file " +
opends/src/server/org/opends/server/monitors/DatabaseEnvironmentMonitor.java
@@ -40,9 +40,9 @@
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.InitializationException;
import org.opends.server.backends.jeb.RootContainer;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentStats;
import com.sleepycat.je.JEVersion;
import com.sleepycat.je.LockStats;
@@ -73,23 +73,23 @@
  private String name;
  /**
   * The JE environment handle to be monitored.
   * The root container to be monitored.
   */
  private Environment environment;
  private RootContainer rootContainer;
  /**
   * Creates a new database environment monitor.
   * @param name The monitor instance name.
   * @param environment A JE environment handle for the database to be
   * @param rootContainer A root container handle for the database to be
   * monitored.
   */
  public DatabaseEnvironmentMonitor(String name, Environment environment)
  public DatabaseEnvironmentMonitor(String name, RootContainer rootContainer)
  {
    super(name + " Monitor Provider");
    this.name = name;
    this.environment = environment;
    this.rootContainer = rootContainer;
  }
@@ -228,9 +228,10 @@
    try
    {
      environmentStats = environment.getStats(statsConfig);
      lockStats = environment.getLockStats(statsConfig);
      transactionStats = environment.getTransactionStats(statsConfig);
      environmentStats = rootContainer.getEnvironmentStats(statsConfig);
      lockStats = rootContainer.getEnvironmentLockStats(statsConfig);
      transactionStats =
          rootContainer.getEnvironmentTransactionStats(statsConfig);
    } catch (DatabaseException e)
    {
      if (debugEnabled())
opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
@@ -2180,7 +2180,7 @@
  /**
   * Export the entries.
   * @throws DirectoryException when an error occured
   * @throws DirectoryException when an error occurred
   */
  protected void exportBackend() throws DirectoryException
  {
@@ -2334,7 +2334,7 @@
   * Sends lDIFEntry entry lines to the export target currently set.
   *
   * @param lDIFEntry The lines for the LDIF entry.
   * @throws IOException when an error occured.
   * @throws IOException when an error occurred.
   */
  public void sendEntryLines(String lDIFEntry) throws IOException
  {
@@ -2697,7 +2697,7 @@
   *
   * @param baseDN The baseDN of the domain to retrieve
   * @return The domain retrieved
   * @throws DirectoryException When an error occured or no domain
   * @throws DirectoryException When an error occurred or no domain
   * match the provided baseDN.
   */
  public static ReplicationDomain retrievesReplicationDomain(DN baseDN)
opends/src/server/org/opends/server/replication/server/ReplicationDbEnv.java
@@ -68,7 +68,7 @@
   * @param path Path where the backing files must be created.
   * @param replicationServer the ReplicationServer that creates this
   *                          ReplicationDbEnv.
   * @throws DatabaseException If a DatabaseException occured that prevented
   * @throws DatabaseException If a DatabaseException occurred that prevented
   *                           the initialization to happen.
   * @throws ReplicationDBException If a replicationServer internal error caused
   *                              a failure of the replicationServer processing.
opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -413,6 +413,30 @@
ds-cfg-index-type: ordering
ds-cfg-index-type: approximate
dn: ds-cfg-backend-id=importRoot,cn=Backends,cn=config
changetype: add
objectClass: top
objectClass: ds-cfg-backend
objectClass: ds-cfg-je-backend
ds-cfg-backend-enabled: true
ds-cfg-backend-class: org.opends.server.backends.jeb.BackendImpl
ds-cfg-backend-id: importRoot
ds-cfg-backend-writability-mode: enabled
ds-cfg-backend-base-dn: dc=importtest, dc=com
ds-cfg-backend-base-dn: dc=importtest1, dc=com
ds-cfg-backend-directory: db_import_test
ds-cfg-backend-mode: 700
ds-cfg-backend-index-entry-limit: 10
ds-cfg-backend-subtree-delete-size-limit: 100000
ds-cfg-backend-preload-time-limit: 0 seconds
ds-cfg-backend-import-temp-directory: importTmp
ds-cfg-backend-import-buffer-size: 256 megabytes
ds-cfg-backend-import-queue-size: 100
ds-cfg-backend-import-pass-size: 0
ds-cfg-backend-import-thread-count: 8
ds-cfg-backend-entries-compressed: false
ds-cfg-backend-deadlock-retry-limit: 10
dn: ds-cfg-backend-id=verifyRoot,cn=Backends,cn=config
changetype: add
objectClass: top
@@ -585,6 +609,7 @@
ds-cfg-index-type: presence
ds-cfg-index-type: equality
ds-cfg-index-type: substring
ds-cfg-index-type: ordering
dn: ds-cfg-index-attribute=mail,cn=Index,ds-cfg-backend-id=indexRoot,cn=Backends,cn=config
changetype: add
@@ -595,6 +620,8 @@
ds-cfg-index-type: equality
ds-cfg-index-type: substring
ds-cfg-index-type: ordering
ds-cfg-index-type: approximate
ds-cfg-index-entry-limit: 1
dn: ds-cfg-index-attribute=member,cn=Index,ds-cfg-backend-id=indexRoot,cn=Backends,cn=config
changetype: add
opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
@@ -180,7 +180,7 @@
    String[] subDirectories = { "bak", "bin", "changelogDb", "classes",
                                "config", "db", "db_verify", "ldif", "lib",
                                "locks", "logs", "db_rebuild", "db_unindexed",
                                "db_index_test" };
                                "db_index_test", "db_import_test"};
    for (String s : subDirectories)
    {
      new File(testRoot, s).mkdir();
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
@@ -30,7 +30,9 @@
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.server.JEBackendCfg;
import org.opends.server.admin.std.server.JEIndexCfg;
import org.opends.server.admin.std.meta.JEBackendCfgDefn;
import org.opends.server.admin.std.meta.JEIndexCfgDefn;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.ModifyOperation;
@@ -735,8 +737,16 @@
    EntryContainer ec =
        backend.getRootContainer().getEntryContainer(DN.decode("dc=test1,dc=com"));
    assertFalse(ec.entryExists(DN.decode("dc=test1,dc=com")));
    assertFalse(ec.entryExists(DN.decode("uid=user.362,dc=test1,dc=com")));
    ec.sharedLock.lock();
    try
    {
      assertFalse(ec.entryExists(DN.decode("dc=test1,dc=com")));
      assertFalse(ec.entryExists(DN.decode("uid=user.362,dc=test1,dc=com")));
    }
    finally
    {
      ec.sharedLock.unlock();
    }
  }
  @Test(dependsOnMethods = {"testAdd", "testSearchIndex",
@@ -760,75 +770,84 @@
    EntryContainer ec =
        backend.getRootContainer().getEntryContainer(DN.decode("ou=People,dc=test,dc=com"));
    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"));
    ec.sharedLock.lock();
    try
    {
      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"));
    DeleteOperation delete = new DeleteOperation(conn,
        conn.nextOperationID(),
        conn.nextMessageID(),
        noControls,
      DeleteOperation delete = new DeleteOperation(conn,
          conn.nextOperationID(),
          conn.nextMessageID(),
          noControls,
        DN.decode("uid=user.539,ou=People,dc=test,dc=com"));
          DN.decode("uid=user.539,ou=People,dc=test,dc=com"));
    backend.deleteEntry(DN.decode("uid=user.539,ou=People,dc=test,dc=com"),
        delete);
      backend.deleteEntry(DN.decode("uid=user.539,ou=People,dc=test,dc=com"),
          delete);
    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")));
    assertFalse(ec.getDN2URI().delete(null,
        DN.decode("uid=user.539,ou=People,dc=test,dc=com")));
      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")));
      assertFalse(ec.getDN2URI().delete(null,
          DN.decode("uid=user.539,ou=People,dc=test,dc=com")));
    attribute = entries.get(0).getAttribute("cn").get(0).getAttributeType();
    index = ec.getAttributeIndex(attribute);
      attribute = entries.get(0).getAttribute("cn").get(0).getAttributeType();
      index = ec.getAttributeIndex(attribute);
    addKeys = new HashSet<ASN1OctetString>();
    presenceIndexer = new PresenceIndexer(index.indexConfig);
    presenceIndexer.indexEntry(null, entry, addKeys);
      addKeys = new HashSet<ASN1OctetString>();
      presenceIndexer = new PresenceIndexer(index.getAttributeType());
      presenceIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.presenceIndex.containsID(null, key, entryID),
          ConditionResult.FALSE);
      addKeys = new HashSet<ASN1OctetString>();
      equalityIndexer = new EqualityIndexer(index.getAttributeType());
      equalityIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.equalityIndex.containsID(null, key, entryID),
          ConditionResult.FALSE);
      addKeys = new HashSet<ASN1OctetString>();
      substringIndexer = new SubstringIndexer(index.getAttributeType(),
                   index.getConfiguration().getIndexSubstringLength());
      substringIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.substringIndex.containsID(null, key, entryID),
          ConditionResult.FALSE);
      addKeys = new HashSet<ASN1OctetString>();
      orderingIndexer = new OrderingIndexer(index.getAttributeType());
      orderingIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.orderingIndex.containsID(null, key, entryID),
          ConditionResult.FALSE);
    }
    assertEquals(index.presenceIndex.containsID(null, key, entryID),
        ConditionResult.FALSE);
    addKeys = new HashSet<ASN1OctetString>();
    equalityIndexer = new EqualityIndexer(index.indexConfig);
    equalityIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    finally
    {
      ec.sharedLock.unlock();
    }
    assertEquals(index.equalityIndex.containsID(null, key, entryID),
        ConditionResult.FALSE);
    addKeys = new HashSet<ASN1OctetString>();
    substringIndexer = new SubstringIndexer(index.indexConfig);
    substringIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.substringIndex.containsID(null, key, entryID),
        ConditionResult.FALSE);
    addKeys = new HashSet<ASN1OctetString>();
    orderingIndexer = new OrderingIndexer(index.indexConfig);
    orderingIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.orderingIndex.containsID(null, key, entryID),
        ConditionResult.FALSE);
  }
  @Test(dependsOnMethods = {"testSearchNotIndexed", "testAdd", "testSearchIndex", "testSearchScope"})
@@ -848,98 +867,104 @@
    EntryContainer ec =
        backend.getRootContainer().getEntryContainer(DN.decode("dc=test,dc=com"));
    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"));
    ec.sharedLock.lock();
    try
    {
      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"));
    assertNotNull(entry);
    LinkedHashSet<AttributeValue> values =
        entry.getAttribute("cn").get(0).getValues();
    for (AttributeValue value : values) {
      assertEquals(value.getStringValue(), "Testing Test");
      assertNotNull(entry);
      LinkedHashSet<AttributeValue> values =
          entry.getAttribute("cn").get(0).getValues();
      for (AttributeValue value : values) {
        assertEquals(value.getStringValue(), "Testing Test");
      }
      values = entry.getAttribute("sn").get(0).getValues();
      for (AttributeValue value : values) {
        assertEquals(value.getStringValue(), "Test");
      }
      values = entry.getAttribute("givenname").get(0).getValues();
      for (AttributeValue value : values) {
        assertEquals(value.getStringValue(), "Testing");
      }
      values = entry.getAttribute("employeenumber").get(0).getValues();
      for (AttributeValue value : values) {
        assertEquals(value.getStringValue(), "777");
      }
      attribute = entry.getAttribute("cn").get(0).getAttributeType();
      index = ec.getAttributeIndex(attribute);
      addKeys = new HashSet<ASN1OctetString>();
      orderingIndexer = new OrderingIndexer(index.getAttributeType());
      orderingIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.orderingIndex.containsID(null, key, entryID),
          ConditionResult.TRUE);
      addKeys = new HashSet<ASN1OctetString>();
      orderingIndexer.indexEntry(null, oldEntry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.orderingIndex.containsID(null, key, entryID),
                   ConditionResult.FALSE);
      addKeys = new HashSet<ASN1OctetString>();
      substringIndexer = new SubstringIndexer(index.getAttributeType(),
                                              index.getConfiguration().getIndexSubstringLength());
      substringIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.substringIndex.containsID(null, key, entryID),
                   ConditionResult.TRUE);
      addKeys = new HashSet<ASN1OctetString>();
      substringIndexer.indexEntry(null, oldEntry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.substringIndex.containsID(null, key, entryID),
                   ConditionResult.FALSE);
      addKeys = new HashSet<ASN1OctetString>();
      equalityIndexer = new EqualityIndexer(index.getAttributeType());
      equalityIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.equalityIndex.containsID(null, key, entryID),
          ConditionResult.TRUE);
      addKeys = new HashSet<ASN1OctetString>();
      equalityIndexer.indexEntry(null, oldEntry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.equalityIndex.containsID(null, key, entryID),
          ConditionResult.FALSE);
    }
    values = entry.getAttribute("sn").get(0).getValues();
    for (AttributeValue value : values) {
      assertEquals(value.getStringValue(), "Test");
    finally
    {
      ec.sharedLock.unlock();
    }
    values = entry.getAttribute("givenname").get(0).getValues();
    for (AttributeValue value : values) {
      assertEquals(value.getStringValue(), "Testing");
    }
    values = entry.getAttribute("employeenumber").get(0).getValues();
    for (AttributeValue value : values) {
      assertEquals(value.getStringValue(), "777");
    }
    attribute = entry.getAttribute("cn").get(0).getAttributeType();
    index = ec.getAttributeIndex(attribute);
    addKeys = new HashSet<ASN1OctetString>();
    orderingIndexer = new OrderingIndexer(index.indexConfig);
    orderingIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.orderingIndex.containsID(null, key, entryID),
        ConditionResult.TRUE);
    addKeys = new HashSet<ASN1OctetString>();
    orderingIndexer = new OrderingIndexer(index.indexConfig);
    orderingIndexer.indexEntry(null, oldEntry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.orderingIndex.containsID(null, key, entryID),
        ConditionResult.FALSE);
    addKeys = new HashSet<ASN1OctetString>();
    substringIndexer = new SubstringIndexer(index.indexConfig);
    substringIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.substringIndex.containsID(null, key, entryID),
        ConditionResult.TRUE);
    addKeys = new HashSet<ASN1OctetString>();
    substringIndexer = new SubstringIndexer(index.indexConfig);
    substringIndexer.indexEntry(null, oldEntry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.substringIndex.containsID(null, key, entryID),
        ConditionResult.FALSE);
    addKeys = new HashSet<ASN1OctetString>();
    equalityIndexer = new EqualityIndexer(index.indexConfig);
    equalityIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.equalityIndex.containsID(null, key, entryID),
        ConditionResult.TRUE);
    addKeys = new HashSet<ASN1OctetString>();
    equalityIndexer = new EqualityIndexer(index.indexConfig);
    equalityIndexer.indexEntry(null, oldEntry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.equalityIndex.containsID(null, key, entryID),
        ConditionResult.FALSE);
  }
@@ -960,120 +985,128 @@
    EntryContainer ec =
        backend.getRootContainer().getEntryContainer(DN.decode("dc=test,dc=com"));
    ec.sharedLock.lock();
    try
    {
      ArrayList<Modification> modifications = new ArrayList<Modification>();
      modifications.add(new Modification(ModificationType.ADD, new
          Attribute("title", "debugger")));
      modifications.add(new Modification(ModificationType.DELETE, new
          Attribute("cn", "Aaren Atp")));
      modifications.add(new Modification(ModificationType.ADD, new
          Attribute("cn", "Aaren Rigor")));
      modifications.add(new Modification(ModificationType.ADD, new
          Attribute("cn", "Aarenister Rigor")));
      modifications.add(new Modification(ModificationType.REPLACE, new
          Attribute("employeenumber", "222")));
    ArrayList<Modification> modifications = new ArrayList<Modification>();
    modifications.add(new Modification(ModificationType.ADD, new
        Attribute("title", "debugger")));
    modifications.add(new Modification(ModificationType.DELETE, new
        Attribute("cn", "Aaren Atp")));
    modifications.add(new Modification(ModificationType.ADD, new
        Attribute("cn", "Aaren Rigor")));
    modifications.add(new Modification(ModificationType.ADD, new
        Attribute("cn", "Aarenister Rigor")));
    modifications.add(new Modification(ModificationType.REPLACE, new
        Attribute("employeenumber", "222")));
      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"));
    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"));
    assertNotNull(entryID);
      assertNotNull(entryID);
    attribute = newEntry.getAttribute("title").get(0).getAttributeType();
    index = ec.getAttributeIndex(attribute);
      attribute = newEntry.getAttribute("title").get(0).getAttributeType();
      index = ec.getAttributeIndex(attribute);
    //This current entry in the DB shouldn't be in the presence index.
    addKeys = new HashSet<ASN1OctetString>();
    addKeys.add(new ASN1OctetString(AttributeIndex.presenceKey.getData()));
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
      //This current entry in the DB shouldn't be in the presence index.
      addKeys = new HashSet<ASN1OctetString>();
      addKeys.add(new ASN1OctetString(AttributeIndex.presenceKey.getData()));
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.presenceIndex.containsID(null, key, entryID),
          ConditionResult.FALSE);
      ArrayList<Control> noControls = new ArrayList<Control>(0);
      InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
      ModifyOperation modifyOp = new ModifyOperation(conn,
          conn.nextOperationID(),
          conn.nextMessageID(),
          noControls,
          DN.decode("uid=user.1,ou=People,dc=test,dc=com"),
          modifications);
      backend.replaceEntry(newEntry, modifyOp);
      entry = ec.getEntry(DN.decode("uid=user.1,ou=People,dc=test,dc=com"));
      assertTrue(entry.getAttribute("title").contains(new
          Attribute("title", "debugger")));
      assertTrue(entry.getAttribute("cn").get(0).getValues().contains(
          new AttributeValue(
              entry.getAttribute("cn").get(0).getAttributeType(),
              "Aaren Rigor")));
      assertTrue(entry.getAttribute("cn").get(0).getValues().contains(
          new AttributeValue(
              entry.getAttribute("cn").get(0).getAttributeType(),
              "Aarenister Rigor")));
      assertFalse(entry.getAttribute("cn").get(0).getValues().contains(
          new AttributeValue(
              entry.getAttribute("cn").get(0).getAttributeType(),
              "Aaren Atp")));
      assertTrue(entry.getAttribute("employeenumber").contains(new
          Attribute("employeenumber", "222")));
      assertFalse(entry.getAttribute("employeenumber").contains(new
          Attribute("employeenumber", "1")));
      addKeys = new HashSet<ASN1OctetString>();
      presenceIndexer = new PresenceIndexer(index.getAttributeType());
      presenceIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.presenceIndex.containsID(null, key, entryID),
          ConditionResult.TRUE);
      addKeys = new HashSet<ASN1OctetString>();
      orderingIndexer = new OrderingIndexer(index.getAttributeType());
      orderingIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.orderingIndex.containsID(null, key, entryID),
          ConditionResult.TRUE);
      addKeys = new HashSet<ASN1OctetString>();
      equalityIndexer = new EqualityIndexer(index.getAttributeType());
      equalityIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.equalityIndex.containsID(null, key, entryID),
          ConditionResult.TRUE);
      addKeys = new HashSet<ASN1OctetString>();
      substringIndexer = new SubstringIndexer(index.getAttributeType(),
                   index.getConfiguration().getIndexSubstringLength());
      substringIndexer.indexEntry(null, entry, addKeys);
      key = new DatabaseEntry();
      for (ASN1OctetString keyBytes : addKeys) {
        key.setData(keyBytes.value());
      }
      assertEquals(index.substringIndex.containsID(null, key, entryID),
          ConditionResult.TRUE);
    }
    assertEquals(index.presenceIndex.containsID(null, key, entryID),
        ConditionResult.FALSE);
    ArrayList<Control> noControls = new ArrayList<Control>(0);
    InternalClientConnection conn =
        InternalClientConnection.getRootConnection();
    ModifyOperation modifyOp = new ModifyOperation(conn,
        conn.nextOperationID(),
        conn.nextMessageID(),
        noControls,
        DN.decode("uid=user.1,ou=People,dc=test,dc=com"),
        modifications);
    backend.replaceEntry(newEntry, modifyOp);
    entry = ec.getEntry(DN.decode("uid=user.1,ou=People,dc=test,dc=com"));
    assertTrue(entry.getAttribute("title").contains(new
        Attribute("title", "debugger")));
    assertTrue(entry.getAttribute("cn").get(0).getValues().contains(
        new AttributeValue(
            entry.getAttribute("cn").get(0).getAttributeType(),
            "Aaren Rigor")));
    assertTrue(entry.getAttribute("cn").get(0).getValues().contains(
        new AttributeValue(
            entry.getAttribute("cn").get(0).getAttributeType(),
            "Aarenister Rigor")));
    assertFalse(entry.getAttribute("cn").get(0).getValues().contains(
        new AttributeValue(
            entry.getAttribute("cn").get(0).getAttributeType(),
            "Aaren Atp")));
    assertTrue(entry.getAttribute("employeenumber").contains(new
        Attribute("employeenumber", "222")));
    assertFalse(entry.getAttribute("employeenumber").contains(new
        Attribute("employeenumber", "1")));
    addKeys = new HashSet<ASN1OctetString>();
    presenceIndexer = new PresenceIndexer(index.indexConfig);
    presenceIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    finally
    {
      ec.sharedLock.unlock();
    }
    assertEquals(index.presenceIndex.containsID(null, key, entryID),
        ConditionResult.TRUE);
    addKeys = new HashSet<ASN1OctetString>();
    orderingIndexer = new OrderingIndexer(index.indexConfig);
    orderingIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.orderingIndex.containsID(null, key, entryID),
        ConditionResult.TRUE);
    addKeys = new HashSet<ASN1OctetString>();
    equalityIndexer = new EqualityIndexer(index.indexConfig);
    equalityIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.equalityIndex.containsID(null, key, entryID),
        ConditionResult.TRUE);
    addKeys = new HashSet<ASN1OctetString>();
    substringIndexer = new SubstringIndexer(index.indexConfig);
    substringIndexer.indexEntry(null, entry, addKeys);
    key = new DatabaseEntry();
    for (ASN1OctetString keyBytes : addKeys) {
      key.setData(keyBytes.value());
    }
    assertEquals(index.substringIndex.containsID(null, key, entryID),
        ConditionResult.TRUE);
  }
@@ -1081,21 +1114,29 @@
  public void testModifyDN() throws Exception {
    EntryContainer ec =
        backend.getRootContainer().getEntryContainer(DN.decode("dc=test,dc=com"));
    Entry entry =
        ec.getEntry(DN.decode("uid=user.2,ou=People,dc=test,dc=com"));
    entry.setDN(DN.decode("cn=Abbey Abbie,ou=People,dc=test,dc=com"));
    ec.sharedLock.lock();
    try
    {
      Entry entry =
          ec.getEntry(DN.decode("uid=user.2,ou=People,dc=test,dc=com"));
      entry.setDN(DN.decode("cn=Abbey Abbie,ou=People,dc=test,dc=com"));
    backend.renameEntry(DN.decode("uid=user.2,ou=People,dc=test,dc=com"),
        entry, null);
      backend.renameEntry(DN.decode("uid=user.2,ou=People,dc=test,dc=com"),
          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(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")));
    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")));
      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")));
    }
    finally
    {
      ec.sharedLock.unlock();
    }
  }
  @Test(dependsOnMethods = {"testSearchNotIndexed", "testAdd", "testSearchIndex",
@@ -1108,39 +1149,46 @@
    EntryContainer ec =
        backend.getRootContainer().getEntryContainer(DN.decode("dc=test,dc=com"));
    EntryID newSuperiorID = ec.getDN2ID().get(null, DN.decode("ou=JEB Testers,dc=test,dc=com"));
    EntryID oldID = ec.getDN2ID().get(null,
        DN.decode("ou=People,dc=test,dc=com"));
    assertTrue(newSuperiorID.compareTo(oldID) > 0);
    ec.sharedLock.lock();
    try
    {
      EntryID newSuperiorID = ec.getDN2ID().get(null, DN.decode("ou=JEB Testers,dc=test,dc=com"));
      EntryID oldID = ec.getDN2ID().get(null,
          DN.decode("ou=People,dc=test,dc=com"));
      assertTrue(newSuperiorID.compareTo(oldID) > 0);
    ArrayList<Control> noControls = new ArrayList<Control>(0);
    InternalClientConnection conn =
        InternalClientConnection.getRootConnection();
      ArrayList<Control> noControls = new ArrayList<Control>(0);
      InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
    ModifyDNOperation modifyDN = new ModifyDNOperation(conn,
        conn.nextOperationID(),
        conn.nextMessageID(),
        noControls,
        DN.decode("ou=People,dc=test,dc=com"),
        RDN.decode("ou=Good People"),
        false,
        DN.decode("ou=JEB Testers,dc=test,dc=com"));
      ModifyDNOperation modifyDN = new ModifyDNOperation(conn,
          conn.nextOperationID(),
          conn.nextMessageID(),
          noControls,
          DN.decode("ou=People,dc=test,dc=com"),
          RDN.decode("ou=Good People"),
          false,
          DN.decode("ou=JEB Testers,dc=test,dc=com"));
    modifyDN.run();
      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"));
    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"));
    assertTrue(newSubordinateID.compareTo(newID) > 0);
      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"));
      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"));
      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")));
      assertNull(backend.getEntry(DN.decode("ou=People,dc=test,dc=com")));
      assertNull(ec.getDN2ID().get(null,
          DN.decode("ou=People,dc=test,dc=com")));
    }
    finally
    {
      ec.sharedLock.unlock();
    }
  }
  @Test(dependsOnMethods = {"testModifyDN",
@@ -1148,7 +1196,7 @@
      "testModifyEntry", "testModifyDN", "testDeleteSubtree",
      "testDeleteEntry", "testAddNoParent", "testAdd",
      "testSearchNotIndexed",
      "testModifyDNNewSuperior"})
      "testModifyDNNewSuperior", "testApplyIndexConfig"})
  public void testApplyConfig() throws Exception {
    Entry configEntry = TestCaseUtils.makeEntry(
        "dn: ds-cfg-backend-id=indexRoot,cn=Backends,cn=config",
@@ -1177,6 +1225,267 @@
    assertNotNull(rootContainer.getEntryContainer(DN.decode("dc=newsuffix,dc=com")));
  }
  @Test(dependsOnMethods = {"testModifyDN",
      "testSearchScope", "testSearchIndex", "testReplaceEntry",
      "testModifyEntry", "testModifyDN", "testDeleteSubtree",
      "testDeleteEntry", "testAddNoParent", "testAdd",
      "testSearchNotIndexed",
      "testModifyDNNewSuperior"})
  public void testApplyIndexConfig() throws Exception {
    Entry configEntry = TestCaseUtils.makeEntry(
        "dn: ds-cfg-index-attribute=givenName,cn=Index," +
            "ds-cfg-backend-id=indexRoot,cn=Backends,cn=config",
        "objectClass: top",
        "objectClass: ds-cfg-je-index",
        "ds-cfg-index-attribute: givenName",
        "ds-cfg-index-type: approximate");
    JEIndexCfg cfg = AdminTestCaseUtils.getConfiguration(
        JEIndexCfgDefn.getInstance(), configEntry);
    RootContainer rootContainer = backend.getRootContainer();
    EntryContainer ec = rootContainer.getEntryContainer(DN.decode("dc=test,dc=com"));
    AttributeIndex index =
        ec.getAttributeIndex(DirectoryServer.getAttributeType("givenname"));
    ConfigChangeResult ccr = index.applyConfigurationChange(cfg);
    assertTrue(ccr.getResultCode().equals(ResultCode.SUCCESS));
    assertTrue(ccr.adminActionRequired());
    assertFalse(ccr.getMessages().isEmpty());
    assertNull(index.equalityIndex);
    assertNull(index.presenceIndex);
    assertNull(index.substringIndex);
    assertNull(index.orderingIndex);
    assertNotNull(index.approximateIndex);
    ArrayList<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
    ec.listDatabases(databases);
    boolean eqfound = false;
    boolean prfound = false;
    boolean subfound = false;
    boolean orfound = false;
    boolean apfound = false;
    for(DatabaseContainer dc : databases)
    {
      if(dc.getName().toLowerCase().contains("givenname.approximate"))
      {
        apfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.presence"))
      {
        prfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.substring"))
      {
        subfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.ordering"))
      {
        orfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.equality"))
      {
        eqfound = true;
      }
    }
    assertFalse(eqfound);
    assertFalse(prfound);
    assertFalse(subfound);
    assertFalse(orfound);
    assertTrue(apfound);
    InternalClientConnection conn =
        InternalClientConnection.getRootConnection();
    LinkedHashSet<String> attribs = new LinkedHashSet<String>();
    attribs.add(ATTR_DEBUG_SEARCH_INDEX);
    InternalSearchOperation search =
        conn.processSearch(DN.decode("dc=test,dc=com"),
                           SearchScope.SUBORDINATE_SUBTREE,
                           DereferencePolicy.NEVER_DEREF_ALIASES,
                           0,
                           0,
                           false,
                           LDAPFilter.decode("(givenName~=Aaccf)").
                               toSearchFilter(),
                           attribs);
    LinkedList<SearchResultEntry> result = search.getSearchEntries();
    //No indexes should be used.
    String debugString =
        result.get(0).getAttribute("debugsearchindex").get(0).getValues().toString();
    assertTrue(debugString.contains("NOT-INDEXED"));
    configEntry = TestCaseUtils.makeEntry(
        "dn: ds-cfg-index-attribute=givenName,cn=Index," +
            "ds-cfg-backend-id=indexRoot,cn=Backends,cn=config",
        "objectClass: top",
        "objectClass: ds-cfg-je-index",
        "ds-cfg-index-attribute: givenName",
        "ds-cfg-index-type: equality",
        "ds-cfg-index-type: presence",
        "ds-cfg-index-type: ordering",
        "ds-cfg-index-type: substring");
    cfg = AdminTestCaseUtils.getConfiguration(
        JEIndexCfgDefn.getInstance(), configEntry);
    ccr = index.applyConfigurationChange(cfg);
    assertTrue(ccr.getResultCode().equals(ResultCode.SUCCESS));
    assertTrue(ccr.adminActionRequired());
    assertFalse(ccr.getMessages().isEmpty());
    assertNotNull(index.equalityIndex);
    assertNotNull(index.presenceIndex);
    assertNotNull(index.substringIndex);
    assertNotNull(index.orderingIndex);
    assertNull(index.approximateIndex);
    databases = new ArrayList<DatabaseContainer>();
    ec.listDatabases(databases);
    eqfound = false;
    prfound = false;
    subfound = false;
    orfound = false;
    apfound = false;
    for(DatabaseContainer dc : databases)
    {
      if(dc.getName().toLowerCase().contains("givenname.approximate"))
      {
        apfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.presence"))
      {
        prfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.substring"))
      {
        subfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.ordering"))
      {
        orfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.equality"))
      {
        eqfound = true;
      }
    }
    assertTrue(eqfound);
    assertTrue(prfound);
    assertTrue(subfound);
    assertTrue(orfound);
    assertFalse(apfound);
    // Delete the entries attribute index.
    ccr = ec.applyConfigurationDelete(cfg);
    assertTrue(ccr.getResultCode().equals(ResultCode.SUCCESS));
    assertNull(ec.getAttributeIndex(
        DirectoryServer.getAttributeType("givenname")));
    databases = new ArrayList<DatabaseContainer>();
    ec.listDatabases(databases);
    for(DatabaseContainer dc : databases)
    {
      assertFalse(dc.getName().toLowerCase().contains("givenname"));
    }
    // Add it back
    ccr = ec.applyConfigurationAdd(cfg);
    assertTrue(ccr.getResultCode().equals(ResultCode.SUCCESS));
    assertTrue(ccr.adminActionRequired());
    assertFalse(ccr.getMessages().isEmpty());
    assertNotNull(ec.getAttributeIndex(
        DirectoryServer.getAttributeType("givenname")));
    databases = new ArrayList<DatabaseContainer>();
    ec.listDatabases(databases);
    eqfound = false;
    prfound = false;
    subfound = false;
    orfound = false;
    apfound = false;
    for(DatabaseContainer dc : databases)
    {
      if(dc.getName().toLowerCase().contains("givenname.approximate"))
      {
        apfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.presence"))
      {
        prfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.substring"))
      {
        subfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.ordering"))
      {
        orfound = true;
      }
      if(dc.getName().toLowerCase().contains("givenname.equality"))
      {
        eqfound = true;
      }
    }
    assertTrue(eqfound);
    assertTrue(prfound);
    assertTrue(subfound);
    assertTrue(orfound);
    assertFalse(apfound);
    // Make sure changing the index entry limit on an index where the limit
    // is already exceeded causes warnings.
    configEntry = TestCaseUtils.makeEntry(
        "dn: ds-cfg-index-attribute=mail,cn=Index," +
            "ds-cfg-backend-id=indexRoot,cn=Backends,cn=config",
        "objectClass: top",
        "objectClass: ds-cfg-je-index",
        "ds-cfg-index-attribute: mail",
        "ds-cfg-index-type: presence",
        "ds-cfg-index-type: equality",
        "ds-cfg-index-type: ordering",
        "ds-cfg-index-type: substring",
        "ds-cfg-index-type: approximate",
        "ds-cfg-index-entry-limit: 30");
    cfg = AdminTestCaseUtils.getConfiguration(
        JEIndexCfgDefn.getInstance(), configEntry);
    // Make sure removing a index entry limit for an index makes it use the
    // backend wide setting.
    index =
        ec.getAttributeIndex(DirectoryServer.getAttributeType("mail"));
    ccr = index.applyConfigurationChange(cfg);
    assertTrue(ccr.getResultCode().equals(ResultCode.SUCCESS));
    assertTrue(ccr.adminActionRequired());
    assertFalse(ccr.getMessages().isEmpty());
    configEntry = TestCaseUtils.makeEntry(
        "dn: ds-cfg-index-attribute=mail,cn=Index," +
            "ds-cfg-backend-id=indexRoot,cn=Backends,cn=config",
        "objectClass: top",
        "objectClass: ds-cfg-je-index",
        "ds-cfg-index-attribute: mail",
        "ds-cfg-index-type: presence",
        "ds-cfg-index-type: equality",
        "ds-cfg-index-type: ordering",
        "ds-cfg-index-type: substring",
        "ds-cfg-index-type: approximate");
    cfg = AdminTestCaseUtils.getConfiguration(
        JEIndexCfgDefn.getInstance(), configEntry);
   index =
        ec.getAttributeIndex(DirectoryServer.getAttributeType("mail"));
    ccr = index.applyConfigurationChange(cfg);
    assertTrue(ccr.getResultCode().equals(ResultCode.SUCCESS));
    assertFalse(ccr.adminActionRequired());
    assertTrue(ccr.getMessages().isEmpty());
  }
  @Test(dependsOnMethods = {"testDeleteEntry", "testSearchScope",
      "testSearchIndex"})
  public void testSearchNotIndexed() throws Exception {
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestEntryContainer.java
@@ -32,6 +32,8 @@
import java.util.List;
import org.opends.server.TestCaseUtils;
import org.opends.server.api.Backend;
import org.opends.server.core.DirectoryServer;
import org.opends.server.admin.std.server.JEBackendCfg;
import org.opends.server.admin.std.meta.JEBackendCfgDefn;
import org.opends.server.admin.server.AdminTestCaseUtils;
@@ -47,6 +49,9 @@
 * EntryContainer tester.
 */
public class TestEntryContainer extends JebTestCase {
  private  String beID="userRoot";
  private BackendImpl be;
  private static final String ldifString = "dn: dc=com\n"
      + "objectClass: top\n" + "objectClass: domain\n" + "\n"
      + "dn: dc=example,dc=com\n" + "objectClass: top\n"
@@ -113,9 +118,6 @@
      + "cn;lang-en: Rodney Ogasawara\n"
      + "title;lang-en: Sales, Director\n" + "\n" + "";
  private File tempDir;
  private String homeDirName;
  private List<Entry> entryList;
  private long calculatedHighestID = 0;
@@ -132,9 +134,6 @@
    // sure the server is started.
    TestCaseUtils.startServer();
    tempDir = TestCaseUtils.createTemporaryDirectory("jebtest");
    homeDirName = tempDir.getAbsolutePath();
    // Create a set of entries
    entryList = TestCaseUtils.entriesFromLdifString(ldifString);
@@ -150,7 +149,7 @@
   */
  @AfterClass
  public void tearDown() throws Exception {
    TestCaseUtils.deleteDirectory(tempDir);
    TestCaseUtils.clearJEBackend(false, beID, "dc=example,dc=com");
  }
  /**
@@ -161,46 +160,29 @@
   */
  @Test()
  public void test1() throws Exception {
    EnvManager.createHomeDir(homeDirName);
    Entry configEntry = TestCaseUtils.makeEntry(
         "dn: ds-cfg-backend-id=userRoot,cn=Backends,cn=config",
              "objectClass: top",
              "objectClass: ds-cfg-backend",
              "objectClass: ds-cfg-je-backend",
              "ds-cfg-backend-enabled: true",
              "ds-cfg-backend-class: " +
                   "org.opends.server.backends.jeb.BackendImpl",
              "ds-cfg-backend-id: userRoot",
              "ds-cfg-backend-writability-mode: enabled",
              "ds-cfg-backend-base-dn: dc=com",
              "ds-cfg-backend-directory:: " +
                   Base64.encode(homeDirName.getBytes()),
              "ds-cfg-backend-import-temp-directory: importTmp");
    JEBackendCfg cfg = AdminTestCaseUtils.getConfiguration(
         JEBackendCfgDefn.getInstance(), configEntry);
    Config backendConfig = new Config();
    backendConfig.initializeConfig(cfg);
    RootContainer rootContainer = new RootContainer(backendConfig, null);
    rootContainer.open(new File(homeDirName),
                       new FilePermission(true, true, true),
                       false, true, true, false, true, true);
    TestCaseUtils.clearJEBackend(false, beID, null);
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    RootContainer rootContainer = be.getRootContainer();
    EntryContainer entryContainer =
        rootContainer.openEntryContainer(DN.decode("dc=com"));
        rootContainer.getEntryContainer(DN.decode("dc=example,dc=com"));
    EntryID actualHighestID = entryContainer.getHighestEntryID();
    assertTrue(actualHighestID.equals(new EntryID(0)));
    entryContainer.sharedLock.lock();
    try
    {
      EntryID actualHighestID = entryContainer.getHighestEntryID();
      assertTrue(actualHighestID.equals(new EntryID(0)));
    for (Entry entry : entryList) {
      entryContainer.addEntry(entry, null);
      Entry afterEntry = entryContainer.getEntry(entry.getDN());
      assertTrue(afterEntry != null);
      for (Entry entry : entryList) {
        entryContainer.addEntry(entry, null);
        Entry afterEntry = entryContainer.getEntry(entry.getDN());
        assertTrue(afterEntry != null);
      }
      actualHighestID = entryContainer.getHighestEntryID();
      assertTrue(actualHighestID.equals(new EntryID(calculatedHighestID)));
    }
    actualHighestID = entryContainer.getHighestEntryID();
    assertTrue(actualHighestID.equals(new EntryID(calculatedHighestID)));
    rootContainer.close();
    EnvManager.removeFiles(homeDirName);
    finally
    {
      entryContainer.sharedLock.unlock();
    }
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestImportJob.java
@@ -27,6 +27,7 @@
package org.opends.server.backends.jeb;
import org.opends.server.TestCaseUtils;
import org.opends.server.tasks.TaskUtils;
import org.opends.server.admin.std.server.JEBackendCfg;
import org.opends.server.admin.std.meta.JEBackendCfgDefn;
import org.opends.server.admin.server.AdminTestCaseUtils;
@@ -51,33 +52,31 @@
public class TestImportJob extends JebTestCase
{
  private  String beID="importRoot";
  private File tempDir;
  private String homeDirName;
  private String importDirName;
  private LDIFImportConfig ldifImportConfig;
  private Config backendConfig;
  private DN[] baseDNs;
  private BackendImpl be;
  private  String errorCount="verify-error-count";
  private String top = "dn: dc=example,dc=com\n" +
  private String top = "dn: dc=importtest,dc=com\n" +
      "objectclass: top\n" +
      "objectclass: domain\n" +
      "dc: example\n" +
      "\n" +
      "dn: ou=People,dc=example,dc=com\n" +
      "dn: ou=People,dc=importtest,dc=com\n" +
      "objectclass: top\n" +
      "objectclass: organizationalUnit\n" +
      "ou: People\n" +
      "\n" +
      "dn: dc=example1,dc=com\n" +
      "dn: dc=importtest1,dc=com\n" +
      "objectclass: top\n" +
      "objectclass: domain\n" +
      "dc: example1\n";
  private String entries1 =
      "dn: uid=user.0,ou=People,dc=example,dc=com\n" +
      "dn: uid=user.0,ou=People,dc=importtest,dc=com\n" +
      "objectClass: top\n" +
      "objectClass: person\n" +
      "objectClass: organizationalPerson\n" +
@@ -101,7 +100,7 @@
      "postalAddress: Aaccf Amar$99262 Eleventh Street$Salem, NM  36530\n" +
      "description: This is the description for Aaccf Amar.\n" +
      "\n" +
      "dn: uid=user.539,ou=People,dc=example,dc=com\n" +
      "dn: uid=user.539,ou=People,dc=importtest,dc=com\n" +
      "objectClass: top\n" +
      "objectClass: person\n" +
      "objectClass: organizationalPerson\n" +
@@ -125,7 +124,7 @@
      "postalAddress: Ardyth Bainton$81170 Taylor Street$Syracuse, WV  93507\n" +
      "description: This is the description for Ardyth Bainton.\n" +
      "\n" +
      "dn: uid=user.446,dc=example1,dc=com\n" +
      "dn: uid=user.446,dc=importtest1,dc=com\n" +
      "objectClass: top\n" +
      "objectClass: person\n" +
      "objectClass: organizationalPerson\n" +
@@ -149,7 +148,7 @@
      "postalAddress: Annalee Avard$46168 Mill Street$Charleston, CO  60948\n" +
      "description: This is the description for Annalee Avard.\n" +
      "\n" +
      "dn: uid=user.362,dc=example1,dc=com\n" +
      "dn: uid=user.362,dc=importtest1,dc=com\n" +
      "objectClass: top\n" +
      "objectClass: person\n" +
      "objectClass: organizationalPerson\n" +
@@ -174,7 +173,7 @@
      "description: This is the description for Andaree Asawa.\n";
  private String replacement1 =
      "dn: uid=user.446,dc=example1,dc=com\n" +
      "dn: uid=user.446,dc=importtest1,dc=com\n" +
      "objectClass: top\n" +
      "objectClass: person\n" +
      "objectClass: organizationalPerson\n" +
@@ -208,7 +207,6 @@
    tempDir = TestCaseUtils.createTemporaryDirectory("jebimporttest");
    homeDirName = tempDir.getAbsolutePath();
    importDirName = homeDirName + File.separator + "importTmp";
    EnvManager.createHomeDir(homeDirName);
@@ -233,56 +231,13 @@
    writer.close();
    ldifFile.close();
    Entry configEntry = TestCaseUtils.makeEntry(
      "dn: ds-cfg-backend-id=userRoot,cn=Backends,cn=config",
      "objectClass: top",
      "objectClass: ds-cfg-backend",
      "objectClass: ds-cfg-je-backend",
      "ds-cfg-backend-base-dn: dc=example,dc=com",
      "ds-cfg-backend-base-dn: dc=example1,dc=com",
      "ds-cfg-backend-writability-mode: enabled",
      "ds-cfg-backend-enabled: true",
      "ds-cfg-backend-class: org.opends.server.backends.jeb.BackendImpl",
      "ds-cfg-backend-id: userRoot",
      "ds-cfg-backend-directory:: " + Base64.encode(homeDirName.getBytes()),
      "ds-cfg-backend-import-temp-directory:: " + Base64.encode(importDirName.getBytes()));
    ConfigEntry backendConfigEntry = new ConfigEntry(configEntry, null);
    JEBackendCfg cfg = AdminTestCaseUtils.getConfiguration(
         JEBackendCfgDefn.getInstance(), configEntry);
    Entry indexEntry = TestCaseUtils.makeEntry(
      "dn: cn=Index,ds-cfg-backend-id=userRoot,cn=Backends,cn=config\n" +
      "objectClass: top\n" +
      "objectClass: ds-cfg-branch\n" +
      "cn: Index");
    ConfigEntry indexConfigEntry = new ConfigEntry(indexEntry, backendConfigEntry);
    Entry cnIndexEntry = TestCaseUtils.makeEntry(
      "dn: ds-cfg-index-attribute=cn,cn=Index,ds-cfg-backend-id=userRoot,cn=Backends,cn=config\n" +
      "objectClass: top\n" +
      "objectClass: ds-cfg-je-index\n" +
      "ds-cfg-index-attribute: cn\n" +
      "ds-cfg-index-type: presence\n" +
      "ds-cfg-index-type: equality\n" +
      "ds-cfg-index-type: substring\n" +
      "ds-cfg-index-type: ordering");
    ConfigEntry cnIndexConfigEntry = new ConfigEntry(cnIndexEntry, indexConfigEntry);
    indexConfigEntry.addChild(cnIndexConfigEntry);
    backendConfigEntry.addChild(indexConfigEntry);
    baseDNs = new DN[]
    {
      DN.decode("dc=example,dc=com"),
      DN.decode("dc=example1,dc=com")
      DN.decode("dc=importtest,dc=com"),
      DN.decode("dc=importtest1,dc=com")
    };
    backendConfig = new Config();
    backendConfig.initializeConfig(cfg);
  }
  @AfterClass
@@ -294,6 +249,7 @@
  @Test
  public void testImportAll() throws Exception
  {
    TestCaseUtils.clearJEBackend(false, beID, null);
    ArrayList<String> fileList = new ArrayList<String>();
    fileList.add(homeDirName + File.separator + "top.ldif");
    fileList.add(homeDirName + File.separator + "entries1.ldif");
@@ -305,55 +261,68 @@
    importConfig.setValidateSchema(true);
    importConfig.writeRejectedEntries(rejectedEntries);
    ImportJob importJob = new ImportJob(null, backendConfig, importConfig);
    importJob.importLDIF();
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    TaskUtils.disableBackend(beID);
    try
    {
      be.importLDIF(importConfig);
    }
    finally
    {
      TaskUtils.enableBackend(beID);
    }
    RootContainer rootContainer = new RootContainer(backendConfig, null);
    rootContainer.open();
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    RootContainer rootContainer = be.getRootContainer();
    EntryContainer entryContainer;
    assertTrue(rejectedEntries.size() <= 0);
    for(DN baseDN : baseDNs)
    {
      entryContainer = rootContainer.openEntryContainer(baseDN);
      assertNotNull(entryContainer);
      if(baseDN.toString().equals("dc=example,dc=com"))
      entryContainer = rootContainer.getEntryContainer(baseDN);
      entryContainer.sharedLock.lock();
      try
      {
        assertEquals(entryContainer.getEntryCount(), 4);
        assertTrue(entryContainer.entryExists(baseDN));
        assertTrue(entryContainer.entryExists(DN.decode("ou=People,dc=example,dc=com")));
        assertTrue(entryContainer.entryExists(DN.decode("uid=user.0,ou=People,dc=example,dc=com")));
        assertTrue(entryContainer.entryExists(DN.decode("uid=user.539,ou=People,dc=example,dc=com")));
        assertNotNull(entryContainer);
        VerifyConfig verifyConfig = new VerifyConfig();
        verifyConfig.setBaseDN(baseDN);
        if(baseDN.toString().equals("dc=importtest,dc=com"))
        {
          assertEquals(entryContainer.getEntryCount(), 4);
          assertTrue(entryContainer.entryExists(baseDN));
          assertTrue(entryContainer.entryExists(DN.decode("ou=People,dc=importtest,dc=com")));
          assertTrue(entryContainer.entryExists(DN.decode("uid=user.0,ou=People,dc=importtest,dc=com")));
          assertTrue(entryContainer.entryExists(DN.decode("uid=user.539,ou=People,dc=importtest,dc=com")));
        VerifyJob verifyJob = new VerifyJob(backendConfig, verifyConfig);
        Entry statEntry=bldStatEntry("");
        verifyJob.verifyBackend(rootContainer, statEntry);
        assertEquals(getStatEntryCount(statEntry, errorCount), 0);
          VerifyConfig verifyConfig = new VerifyConfig();
          verifyConfig.setBaseDN(baseDN);
          Entry statEntry=bldStatEntry("");
          be=(BackendImpl) DirectoryServer.getBackend(beID);
          be.verifyBackend(verifyConfig, statEntry);
          assertEquals(getStatEntryCount(statEntry, errorCount), 0);
        }
        else if(baseDN.toString().equals("dc=importtest1,dc=com"))
        {
          assertEquals(entryContainer.getEntryCount(), 3);
          assertTrue(entryContainer.entryExists(baseDN));
          assertTrue(entryContainer.entryExists(DN.decode("uid=user.446,dc=importtest1,dc=com")));
          assertTrue(entryContainer.entryExists(DN.decode("uid=user.362,dc=importtest1,dc=com")));
          VerifyConfig verifyConfig = new VerifyConfig();
          verifyConfig.setBaseDN(baseDN);
          Entry statEntry=bldStatEntry("");
          be=(BackendImpl) DirectoryServer.getBackend(beID);
          be.verifyBackend(verifyConfig, statEntry);
          assertEquals(getStatEntryCount(statEntry, errorCount), 0);
        }
      }
      else if(baseDN.toString().equals("dc=example1,dc=com"))
      finally
      {
        assertEquals(entryContainer.getEntryCount(), 3);
        assertTrue(entryContainer.entryExists(baseDN));
        assertTrue(entryContainer.entryExists(DN.decode("uid=user.446,dc=example1,dc=com")));
        assertTrue(entryContainer.entryExists(DN.decode("uid=user.362,dc=example1,dc=com")));
        entryContainer.sharedLock.unlock();
        VerifyConfig verifyConfig = new VerifyConfig();
        verifyConfig.setBaseDN(baseDN);
        VerifyJob verifyJob = new VerifyJob(backendConfig, verifyConfig);
        Entry statEntry=bldStatEntry("");
        verifyJob.verifyBackend(rootContainer, statEntry);
        assertEquals(getStatEntryCount(statEntry, errorCount), 0);
      }
    }
    rootContainer.close();
  }
  @Test(dependsOnMethods = "testImportAll")
@@ -366,33 +335,47 @@
    importConfig.setValidateSchema(true);
    importConfig.writeRejectedEntries(rejectedEntries);
    ImportJob importJob = new ImportJob(null, backendConfig, importConfig);
    importJob.importLDIF();
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    TaskUtils.disableBackend(beID);
    try
    {
      be.importLDIF(importConfig);
    }
    finally
    {
      TaskUtils.enableBackend(beID);
    }
    RootContainer rootContainer = new RootContainer(backendConfig, null);
    rootContainer.open();
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    RootContainer rootContainer = be.getRootContainer();
    EntryContainer entryContainer;
    entryContainer = rootContainer.openEntryContainer(DN.decode("dc=example1,dc=com"));
    entryContainer = rootContainer.getEntryContainer(DN.decode("dc=importtest1,dc=com"));
    assertNotNull(entryContainer);
    assertTrue(rejectedEntries.size() <= 0);
    Entry entry = entryContainer.getEntry(DN.decode("uid=user.446,dc=example1,dc=com"));
    assertNotNull(entry);
    entryContainer.sharedLock.lock();
    try
    {
      assertTrue(rejectedEntries.size() <= 0);
      Entry entry = entryContainer.getEntry(DN.decode("uid=user.446,dc=importtest1,dc=com"));
      assertNotNull(entry);
    AttributeType attribute = entry.getAttribute("cn").get(0).getAttributeType();
      AttributeType attribute = entry.getAttribute("cn").get(0).getAttributeType();
    assertTrue(entry.hasValue(attribute, null, new AttributeValue(attribute,"Annalee Bogard")));
      assertTrue(entry.hasValue(attribute, null, new AttributeValue(attribute,"Annalee Bogard")));
    VerifyConfig verifyConfig = new VerifyConfig();
    verifyConfig.setBaseDN(DN.decode("dc=example1,dc=com"));
      VerifyConfig verifyConfig = new VerifyConfig();
      verifyConfig.setBaseDN(DN.decode("dc=importtest1,dc=com"));
    VerifyJob verifyJob = new VerifyJob(backendConfig, verifyConfig);
    Entry statEntry=bldStatEntry("");
    verifyJob.verifyBackend(rootContainer, statEntry);
    assertEquals(getStatEntryCount(statEntry, errorCount), 0);
    rootContainer.close();
      Entry statEntry=bldStatEntry("");
      be=(BackendImpl) DirectoryServer.getBackend(beID);
      be.verifyBackend(verifyConfig, statEntry);
      assertEquals(getStatEntryCount(statEntry, errorCount), 0);
    }
    finally
    {
      entryContainer.sharedLock.unlock();
    }
  }
  @Test(dependsOnMethods = "testImportReplaceExisting")
@@ -405,10 +388,17 @@
    importConfig.setValidateSchema(true);
    importConfig.writeRejectedEntries(rejectedEntries);
    ImportJob importJob = new ImportJob(null, backendConfig, importConfig);
    importJob.importLDIF();
    assertTrue(rejectedEntries.toString().contains("uid=user.446,dc=example1,dc=com"));
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    TaskUtils.disableBackend(beID);
    try
    {
      be.importLDIF(importConfig);
    }
    finally
    {
      TaskUtils.enableBackend(beID);
    }
    assertTrue(rejectedEntries.toString().contains("uid=user.446,dc=importtest1,dc=com"));
  }
  @Test(dependsOnMethods = "testImportReplaceExisting")
@@ -419,44 +409,66 @@
    importConfig.setReplaceExistingEntries(false);
    importConfig.setValidateSchema(true);
    ImportJob importJob = new ImportJob(null, backendConfig, importConfig);
    importJob.importLDIF();
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    TaskUtils.disableBackend(beID);
    try
    {
      be.importLDIF(importConfig);
    }
    finally
    {
      TaskUtils.enableBackend(beID);
    }
    importConfig = new LDIFImportConfig(homeDirName + File.separator + "entries1.ldif");
    importConfig.setAppendToExistingData(true);
    importConfig.setReplaceExistingEntries(false);
    importConfig.setValidateSchema(true);
    importJob = new ImportJob(null, backendConfig, importConfig);
    importJob.importLDIF();
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    TaskUtils.disableBackend(beID);
    try
    {
      be.importLDIF(importConfig);
    }
    finally
    {
      TaskUtils.enableBackend(beID);
    }
    RootContainer rootContainer = new RootContainer(backendConfig, null);
    rootContainer.open();
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    RootContainer rootContainer = be.getRootContainer();
    EntryContainer entryContainer;
    for(DN baseDN : baseDNs)
    {
      entryContainer = rootContainer.openEntryContainer(baseDN);
      entryContainer = rootContainer.getEntryContainer(baseDN);
      assertNotNull(entryContainer);
      if(baseDN.toString().equals("dc=example,dc=com"))
      entryContainer.sharedLock.lock();
      try
      {
        assertEquals(entryContainer.getEntryCount(), 4);
        assertTrue(entryContainer.entryExists(baseDN));
        assertTrue(entryContainer.entryExists(DN.decode("ou=People,dc=example,dc=com")));
        assertTrue(entryContainer.entryExists(DN.decode("uid=user.0,ou=People,dc=example,dc=com")));
        assertTrue(entryContainer.entryExists(DN.decode("uid=user.539,ou=People,dc=example,dc=com")));
        if(baseDN.toString().equals("dc=importtest,dc=com"))
        {
          assertEquals(entryContainer.getEntryCount(), 4);
          assertTrue(entryContainer.entryExists(baseDN));
          assertTrue(entryContainer.entryExists(DN.decode("ou=People,dc=importtest,dc=com")));
          assertTrue(entryContainer.entryExists(DN.decode("uid=user.0,ou=People,dc=importtest,dc=com")));
          assertTrue(entryContainer.entryExists(DN.decode("uid=user.539,ou=People,dc=importtest,dc=com")));
        }
        else if(baseDN.toString().equals("dc=importtest1,dc=com"))
        {
          assertEquals(entryContainer.getEntryCount(), 3);
          assertTrue(entryContainer.entryExists(baseDN));
          assertTrue(entryContainer.entryExists(DN.decode("uid=user.446,dc=importtest1,dc=com")));
          assertTrue(entryContainer.entryExists(DN.decode("uid=user.362,dc=importtest1,dc=com")));
        }
      }
      else if(baseDN.toString().equals("dc=example1,dc=com"))
      finally
      {
        assertEquals(entryContainer.getEntryCount(), 3);
        assertTrue(entryContainer.entryExists(baseDN));
        assertTrue(entryContainer.entryExists(DN.decode("uid=user.446,dc=example1,dc=com")));
        assertTrue(entryContainer.entryExists(DN.decode("uid=user.362,dc=example1,dc=com")));
        entryContainer.sharedLock.unlock();
        TaskUtils.enableBackend(beID);
      }
    }
    rootContainer.close();
  }
  @Test(dependsOnMethods = "testImportAll")
@@ -469,10 +481,18 @@
    importConfig.setValidateSchema(true);
    importConfig.writeRejectedEntries(rejectedEntries);
    ImportJob importJob = new ImportJob(null, backendConfig, importConfig);
    importJob.importLDIF();
    be=(BackendImpl) DirectoryServer.getBackend(beID);
    TaskUtils.disableBackend(beID);
    try
    {
      be.importLDIF(importConfig);
    }
    finally
    {
      TaskUtils.enableBackend(beID);
    }
    assertTrue(rejectedEntries.toString().contains("uid=user.446,dc=example1,dc=com"));
    assertTrue(rejectedEntries.toString().contains("uid=user.446,dc=importtest1,dc=com"));
  }
      /**
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java
@@ -213,29 +213,37 @@
  @Test()
  public void testCleanDN2ID() throws Exception {
    preTest(3);
    //Add a junk DN and non-existent entry id to DN2ID index
    DN testDN=DN.decode(junkDN);
    EntryID id=new EntryID(45);
    assertTrue(dn2id.insert(txn, testDN, id));
    //Make two DN keys point at same entry.
    testDN=DN.decode(junkDN1);
    id=new EntryID(3);
    assertTrue(dn2id.insert(txn, testDN, id));
    //Add badDN key with bad entry id
    DatabaseEntry key=
         new DatabaseEntry(StaticUtils.getBytes(badDN));
    DatabaseEntry data =
         new EntryID(37).getDatabaseEntry();
    assertTrue(dn2id.putRaw(txn, key, data));
    //Add DN key with malformed entryID
    key=new DatabaseEntry(StaticUtils.getBytes(junkDN2));
    data= new DatabaseEntry(new byte[3]);
    assertTrue(dn2id.putRaw(txn, key, data));
    //Try to break JebFormat version
    addID2EntryReturnKey(junkDN3, 20, true);
    id=new EntryID(20);
    assertTrue(dn2id.insert(txn, DN.decode(junkDN3), id));
    performBECleanVerify("dn2id", 5);
    eContainer.sharedLock.lock();
    try
    {
      //Add a junk DN and non-existent entry id to DN2ID index
      DN testDN=DN.decode(junkDN);
      EntryID id=new EntryID(45);
      assertTrue(dn2id.insert(txn, testDN, id));
      //Make two DN keys point at same entry.
      testDN=DN.decode(junkDN1);
      id=new EntryID(3);
      assertTrue(dn2id.insert(txn, testDN, id));
      //Add badDN key with bad entry id
      DatabaseEntry key=
           new DatabaseEntry(StaticUtils.getBytes(badDN));
      DatabaseEntry data =
           new EntryID(37).getDatabaseEntry();
      assertTrue(dn2id.putRaw(txn, key, data));
      //Add DN key with malformed entryID
      key=new DatabaseEntry(StaticUtils.getBytes(junkDN2));
      data= new DatabaseEntry(new byte[3]);
      assertTrue(dn2id.putRaw(txn, key, data));
      //Try to break JebFormat version
      addID2EntryReturnKey(junkDN3, 20, true);
      id=new EntryID(20);
      assertTrue(dn2id.insert(txn, DN.decode(junkDN3), id));
      performBECleanVerify("dn2id", 5);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /**
@@ -246,30 +254,38 @@
   */
  @Test() public void testCleanID2Children() throws Exception {
    preTest(3);
    //Add malformed key
    byte[] shortBytes = new byte[3];
    DatabaseEntry key= new DatabaseEntry(shortBytes);
    EntryIDSet idSet=new EntryIDSet();
    id2child.writeKey(txn, key, idSet);
    //Try to break JebFormat version of key entry
    key=addID2EntryReturnKey(junkDN, 4, true);
    idSet=new EntryIDSet(new byte[16], new byte[16]);
    id2child.writeKey(txn, key, idSet);
    //put invalid key -- no EntryID matches
    key= new EntryID(45).getDatabaseEntry();
    id2child.writeKey(txn, key, idSet);
    //invalid ids in id list
    key=addID2EntryReturnKey(junkDN1, 5, false);
    byte[] idBytes=new byte[24];
    //doesn't exist
    idBytes[3] = (byte)0xff;
    //not a child
    idBytes[15] = (byte)1;
    //bad jeb format
    idBytes[23] = (byte) 0x04;
    idSet=new EntryIDSet(null, idBytes);
    id2child.writeKey(txn, key, idSet);
    performBECleanVerify("id2children", 6);
    eContainer.sharedLock.lock();
    try
    {
      //Add malformed key
      byte[] shortBytes = new byte[3];
      DatabaseEntry key= new DatabaseEntry(shortBytes);
      EntryIDSet idSet=new EntryIDSet();
      id2child.writeKey(txn, key, idSet);
      //Try to break JebFormat version of key entry
      key=addID2EntryReturnKey(junkDN, 4, true);
      idSet=new EntryIDSet(new byte[16], new byte[16]);
      id2child.writeKey(txn, key, idSet);
      //put invalid key -- no EntryID matches
      key= new EntryID(45).getDatabaseEntry();
      id2child.writeKey(txn, key, idSet);
      //invalid ids in id list
      key=addID2EntryReturnKey(junkDN1, 5, false);
      byte[] idBytes=new byte[24];
      //doesn't exist
      idBytes[3] = (byte)0xff;
      //not a child
      idBytes[15] = (byte)1;
      //bad jeb format
      idBytes[23] = (byte) 0x04;
      idSet=new EntryIDSet(null, idBytes);
      id2child.writeKey(txn, key, idSet);
      performBECleanVerify("id2children", 6);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /**
@@ -280,31 +296,39 @@
   */
  @Test() public void testCleanID2Subtree() throws Exception {
    preTest(4);
    //break key
    byte[] shortBytes = new byte[3];
    DatabaseEntry key= new DatabaseEntry(shortBytes);
    EntryIDSet idSet=new EntryIDSet();
    id2subtree.writeKey(txn, key, idSet);
    //put invalid ids into entry 3 idlist
    key= new EntryID(3).getDatabaseEntry();
    byte[] idBytes=new byte[16];
    //invalid id
    idBytes[3] = (byte)0xff;
    //non-subordinate
    idBytes[15] = (byte)1;
    idSet=new EntryIDSet(null, idBytes);
    id2subtree.writeKey(txn, key, idSet);
    //Try to break JebFormat version of key entry
    key=addID2EntryReturnKey(junkDN, 4, true);
    idBytes[3]=(byte) 0x04;
    idBytes[15]=(byte)0x00;
    EntryIDSet idSet1=new EntryIDSet(null, idBytes);
    id2subtree.writeKey(txn, key, idSet1);
    //put invalid key -- no EntryID matches
    key= new EntryID(45).getDatabaseEntry();
    idSet=new EntryIDSet(null, idBytes);
    id2subtree.writeKey(txn, key, idSet);
    performBECleanVerify("id2subtree", 7);
    eContainer.sharedLock.lock();
    try
    {
      //break key
      byte[] shortBytes = new byte[3];
      DatabaseEntry key= new DatabaseEntry(shortBytes);
      EntryIDSet idSet=new EntryIDSet();
      id2subtree.writeKey(txn, key, idSet);
      //put invalid ids into entry 3 idlist
      key= new EntryID(3).getDatabaseEntry();
      byte[] idBytes=new byte[16];
      //invalid id
      idBytes[3] = (byte)0xff;
      //non-subordinate
      idBytes[15] = (byte)1;
      idSet=new EntryIDSet(null, idBytes);
      id2subtree.writeKey(txn, key, idSet);
      //Try to break JebFormat version of key entry
      key=addID2EntryReturnKey(junkDN, 4, true);
      idBytes[3]=(byte) 0x04;
      idBytes[15]=(byte)0x00;
      EntryIDSet idSet1=new EntryIDSet(null, idBytes);
      id2subtree.writeKey(txn, key, idSet1);
      //put invalid key -- no EntryID matches
      key= new EntryID(45).getDatabaseEntry();
      idSet=new EntryIDSet(null, idBytes);
      id2subtree.writeKey(txn, key, idSet);
      performBECleanVerify("id2subtree", 7);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /**
@@ -314,35 +338,39 @@
   * @throws Exception if the error count is not equal to 4.
   */
  @Test() public void testCleanAttrIndex() throws Exception {
    String phoneType="telephoneNumber";
    String phoneType="telephonenumber";
    preTest(3);
    //Need to open a second database against this index
    //so we can manipulate it. We can't get the index DB handle
    //any other way.
    DatabaseConfig config = new DatabaseConfig();
    config.setAllowCreate(true);
    config.setTransactional(true);
    Database db=
         eContainer.openDatabase(config, phoneType + ".equality");
    //Add entry with bad JEB format Version
    addID2EntryReturnKey(junkDN, 4, true);
    //Add phone number with various bad id list entryIDs
    byte[] subBytes = StaticUtils.getBytes("0009999999");
    DatabaseEntry key= new DatabaseEntry(subBytes);
    byte[] dataBytes=new byte[32];
    //put duplicate ids in list
    dataBytes[7] = (byte)1;
    dataBytes[15] = (byte)1;
    //put id that doesn't exist
    dataBytes[23] = (byte)0xff;
    //point to bad entry added above
    dataBytes[31] = (byte) 0x04;
    DatabaseEntry data= new DatabaseEntry(dataBytes);
    OperationStatus status = EntryContainer.put(db, txn, key, data);
    assertTrue(status == OperationStatus.SUCCESS);
    //really 5 errors, but duplicate reference doesn't increment error
    //count for some reason
    performBECleanVerify(phoneType, 4);
    eContainer.sharedLock.lock();
    try
    {
      AttributeType attributeType =
          DirectoryServer.getAttributeType(phoneType);
      Index index =
           eContainer.getAttributeIndex(attributeType).equalityIndex;
      //Add entry with bad JEB format Version
      addID2EntryReturnKey(junkDN, 4, true);
      //Add phone number with various bad id list entryIDs
      byte[] subBytes = StaticUtils.getBytes("0009999999");
      DatabaseEntry key= new DatabaseEntry(subBytes);
      byte[] dataBytes=new byte[32];
      //put duplicate ids in list
      dataBytes[7] = (byte)1;
      dataBytes[15] = (byte)1;
      //put id that doesn't exist
      dataBytes[23] = (byte)0xff;
      //point to bad entry added above
      dataBytes[31] = (byte) 0x04;
      DatabaseEntry data= new DatabaseEntry(dataBytes);
      OperationStatus status = index.put(txn, key, data);
      assertTrue(status == OperationStatus.SUCCESS);
      //really 5 errors, but duplicate reference doesn't increment error
      //count for some reason
      performBECleanVerify(phoneType, 4);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /*
@@ -360,26 +388,34 @@
   */
  @Test() public void testVerifyID2Entry() throws Exception {
    preTest(3);
    //Add entry with short id
    byte[] shortBytes = new byte[3];
    DatabaseEntry key= new DatabaseEntry(shortBytes);
    Entry testEntry=bldStatEntry(junkDN);
    byte []entryBytes =
         JebFormat.entryToDatabase(testEntry, new DataConfig());
    DatabaseEntry data= new DatabaseEntry(entryBytes);
    assertTrue(id2entry.putRaw(txn, key, data));
    eContainer.sharedLock.lock();
    try
    {
      //Add entry with short id
      byte[] shortBytes = new byte[3];
      DatabaseEntry key= new DatabaseEntry(shortBytes);
      Entry testEntry=bldStatEntry(junkDN);
      byte []entryBytes =
           JebFormat.entryToDatabase(testEntry, new DataConfig());
      DatabaseEntry data= new DatabaseEntry(entryBytes);
      assertTrue(id2entry.putRaw(txn, key, data));
    //add entry with ramdom bytes
    DatabaseEntry key1= new EntryID(4).getDatabaseEntry();
    byte []eBytes = new byte[459];
    for(int i=0;i<459;i++) {
      eBytes[i]=(byte) (i*2);
      //add entry with ramdom bytes
      DatabaseEntry key1= new EntryID(4).getDatabaseEntry();
      byte []eBytes = new byte[459];
      for(int i=0;i<459;i++) {
        eBytes[i]=(byte) (i*2);
      }
      //set version correctly
      eBytes[0]=0x01;
      DatabaseEntry data1= new DatabaseEntry(eBytes);
      assertTrue(id2entry.putRaw(txn, key1, data1));
      performBECompleteVerify("telephoneNumber", 3);
    }
    //set version correctly
    eBytes[0]=0x01;
    DatabaseEntry data1= new DatabaseEntry(eBytes);
    assertTrue(id2entry.putRaw(txn, key1, data1));
    performBECompleteVerify("telephoneNumber", 3);
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /**
@@ -391,21 +427,29 @@
   */
  @Test() public void testVerifyDN2ID() throws Exception {
    preTest(9);
    //add entry but no corresponding dn2id key
    addID2EntryReturnKey(junkDN, 10, false);
    //entry has dn2id key but its entryID -- don't need key
    addID2EntryReturnKey(junkDN1, 11, false);
    //insert key with bad entry id (45 instead of 10)
    DN testDN=DN.decode(junkDN1);
    EntryID id=new EntryID(45);
    assertTrue(dn2id.insert(txn, testDN, id));
    //entry has no parent in dn2id
    addID2EntryReturnKey(noParentDN, 12, false);
    //add the key/id
    testDN=DN.decode(noParentDN);
    id=new EntryID(12);
    assertTrue(dn2id.insert(txn, testDN, id));
    performBECompleteVerify("dn2id", 3);
    eContainer.sharedLock.lock();
    try
    {
      //add entry but no corresponding dn2id key
      addID2EntryReturnKey(junkDN, 10, false);
      //entry has dn2id key but its entryID -- don't need key
      addID2EntryReturnKey(junkDN1, 11, false);
      //insert key with bad entry id (45 instead of 10)
      DN testDN=DN.decode(junkDN1);
      EntryID id=new EntryID(45);
      assertTrue(dn2id.insert(txn, testDN, id));
      //entry has no parent in dn2id
      addID2EntryReturnKey(noParentDN, 12, false);
      //add the key/id
      testDN=DN.decode(noParentDN);
      id=new EntryID(12);
      assertTrue(dn2id.insert(txn, testDN, id));
      performBECompleteVerify("dn2id", 3);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /**
@@ -417,22 +461,30 @@
   */
  @Test() public void testVerifyID2Children() throws Exception {
    preTest(9);
    //Add dn with no parent
    DatabaseEntry key=addID2EntryReturnKey(noParentDN, 10, false);
    byte[] idBytes=new byte[16];
    idBytes[7]=(byte) 0x0A;
    EntryIDSet idSet=new EntryIDSet(null, idBytes);
    id2child.writeKey(txn, key, idSet);
    //Add child entry - don't worry about key
    addID2EntryReturnKey(cDN, 11, false);
    //Add its parent entry -- need the key
    DatabaseEntry keyp=addID2EntryReturnKey(pDN, 12, false);
    //add parent key/IDSet with bad IDset id
    byte[] idBytesp=new byte[16];
    idBytesp[7]=(byte) 0xFF;
    EntryIDSet idSetp=new EntryIDSet(null, idBytesp);
    id2child.writeKey(txn, keyp, idSetp);
    performBECompleteVerify("id2children", 3);
    eContainer.sharedLock.lock();
    try
    {
      //Add dn with no parent
      DatabaseEntry key=addID2EntryReturnKey(noParentDN, 10, false);
      byte[] idBytes=new byte[16];
      idBytes[7]=(byte) 0x0A;
      EntryIDSet idSet=new EntryIDSet(null, idBytes);
      id2child.writeKey(txn, key, idSet);
      //Add child entry - don't worry about key
      addID2EntryReturnKey(cDN, 11, false);
      //Add its parent entry -- need the key
      DatabaseEntry keyp=addID2EntryReturnKey(pDN, 12, false);
      //add parent key/IDSet with bad IDset id
      byte[] idBytesp=new byte[16];
      idBytesp[7]=(byte) 0xFF;
      EntryIDSet idSetp=new EntryIDSet(null, idBytesp);
      id2child.writeKey(txn, keyp, idSetp);
      performBECompleteVerify("id2children", 3);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /**
@@ -447,13 +499,21 @@
   */
  @Test() public void testVerifyID2Children1() throws Exception {
    preTest(2);
    //Add child entry - don't worry about key
    addID2EntryReturnKey(pDN, 10, false);
    //add parent key/IDSet with null keyset
    EntryIDSet idSetp=new EntryIDSet();
    DatabaseEntry key= new EntryID(2).getDatabaseEntry();
    id2child.writeKey(txn, key, idSetp);
    performBECompleteVerify("id2children", 0);
    eContainer.sharedLock.lock();
    try
    {
      //Add child entry - don't worry about key
      addID2EntryReturnKey(pDN, 10, false);
      //add parent key/IDSet with null keyset
      EntryIDSet idSetp=new EntryIDSet();
      DatabaseEntry key= new EntryID(2).getDatabaseEntry();
      id2child.writeKey(txn, key, idSetp);
      performBECompleteVerify("id2children", 0);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /**
@@ -466,9 +526,17 @@
  @Test
  public void testVerifyID2Subtree() throws Exception {
    preTest(2);
    //Add entry with no parent
    addID2EntryReturnKey(noParentDN, 3, false);
    performBECompleteVerify("id2subtree", 3);
    eContainer.sharedLock.lock();
    try
    {
      //Add entry with no parent
      addID2EntryReturnKey(noParentDN, 3, false);
      performBECompleteVerify("id2subtree", 3);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /**
@@ -483,13 +551,21 @@
  @Test
  public void testVerifyID2Subtree1() throws Exception {
    preTest(2);
    //Add child entry - don't worry about key
    addID2EntryReturnKey(pDN, 3, false);
    //add parent key/IDSet with null keyset
    EntryIDSet idSet=new EntryIDSet();
    DatabaseEntry key= new EntryID(2).getDatabaseEntry();
    id2subtree.writeKey(txn, key, idSet);
    performBECompleteVerify("id2subtree", 1);
    eContainer.sharedLock.lock();
    try
    {
      //Add child entry - don't worry about key
      addID2EntryReturnKey(pDN, 3, false);
      //add parent key/IDSet with null keyset
      EntryIDSet idSet=new EntryIDSet();
      DatabaseEntry key= new EntryID(2).getDatabaseEntry();
      id2subtree.writeKey(txn, key, idSet);
      performBECompleteVerify("id2subtree", 1);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /**
@@ -501,53 +577,58 @@
  @Test() public void testVerifyAttribute() throws Exception {
    String mailType="mail";
    preTest(4);
    //Need to open a second databases against this index
    //so we can manipulate it.
    DatabaseConfig config = new DatabaseConfig();
    config.setAllowCreate(true);
    config.setTransactional(true);
    //Get db handles to each index.
    Database dbEq=
         eContainer.openDatabase(config, mailType + ".equality");
    Database dbPres=
         eContainer.openDatabase(config, mailType + ".presence");
    Database dbSub=
         eContainer.openDatabase(config, mailType + ".substring");
    Database dbOr=
         eContainer.openDatabase(config, mailType + ".ordering");
    //Add invalid idlist ids to both equality and ordering indexes.
    DatabaseEntry key=
         new DatabaseEntry(StaticUtils.getBytes("user.0@example.com"));
    byte[] dataBytes=new byte[16];
    //put duplicate ids in list
    dataBytes[7] = (byte)0xff;
    dataBytes[15] = (byte)0xfe;
    DatabaseEntry data= new DatabaseEntry(dataBytes);
    OperationStatus status = EntryContainer.put(dbEq, txn, key, data);
    assertTrue(status == OperationStatus.SUCCESS);
    status = EntryContainer.put(dbOr, txn, key, data);
    assertTrue(status == OperationStatus.SUCCESS);
    //Add null idlist to both equality and ordering indexes.
    key =
         new DatabaseEntry(StaticUtils.getBytes("user.1@example.com"));
    data= new DatabaseEntry(new EntryIDSet().toDatabase());
    status = EntryContainer.put(dbEq, txn, key, data);
    assertTrue(status == OperationStatus.SUCCESS);
    status = EntryContainer.put(dbOr, txn, key, data);
    assertTrue(status == OperationStatus.SUCCESS);
    //Add invalid idlist ids to presence index.
    key =
         new DatabaseEntry(StaticUtils.getBytes("+"));
    data = new DatabaseEntry(dataBytes);
    status = EntryContainer.put(dbPres, txn, key, data);
    assertTrue(status == OperationStatus.SUCCESS);
    //Add invalid idlist ids to substring index.
    key =
         new DatabaseEntry(StaticUtils.getBytes("@examp"));
    data = new DatabaseEntry(dataBytes);
    status = EntryContainer.put(dbSub, txn, key, data);
    assertTrue(status == OperationStatus.SUCCESS);
    performBECompleteVerify(mailType, 6);
    eContainer.sharedLock.lock();
    try
    {
      AttributeType attributeType =
          DirectoryServer.getAttributeType(mailType);
      //Get db handles to each index.
      Index eqIndex =
          eContainer.getAttributeIndex(attributeType).equalityIndex;
      Index presIndex =
          eContainer.getAttributeIndex(attributeType).presenceIndex;
      Index subIndex =
          eContainer.getAttributeIndex(attributeType).substringIndex;
      Index ordIndex =
          eContainer.getAttributeIndex(attributeType).orderingIndex;
      //Add invalid idlist ids to both equality and ordering indexes.
      DatabaseEntry key=
           new DatabaseEntry(StaticUtils.getBytes("user.0@example.com"));
      byte[] dataBytes=new byte[16];
      //put duplicate ids in list
      dataBytes[7] = (byte)0xff;
      dataBytes[15] = (byte)0xfe;
      DatabaseEntry data= new DatabaseEntry(dataBytes);
      OperationStatus status = eqIndex.put(txn, key, data);
      assertTrue(status == OperationStatus.SUCCESS);
      status = ordIndex.put(txn, key, data);
      assertTrue(status == OperationStatus.SUCCESS);
      //Add null idlist to both equality and ordering indexes.
      key =
           new DatabaseEntry(StaticUtils.getBytes("user.1@example.com"));
      data= new DatabaseEntry(new EntryIDSet().toDatabase());
      status = eqIndex.put(txn, key, data);
      assertTrue(status == OperationStatus.SUCCESS);
      status = ordIndex.put(txn, key, data);
      assertTrue(status == OperationStatus.SUCCESS);
      //Add invalid idlist ids to presence index.
      key =
           new DatabaseEntry(StaticUtils.getBytes("+"));
      data = new DatabaseEntry(dataBytes);
      status = presIndex.put(txn, key, data);
      assertTrue(status == OperationStatus.SUCCESS);
      //Add invalid idlist ids to substring index.
      key =
           new DatabaseEntry(StaticUtils.getBytes("@examp"));
      data = new DatabaseEntry(dataBytes);
      status = subIndex.put(txn, key, data);
      assertTrue(status == OperationStatus.SUCCESS);
      performBECompleteVerify(mailType, 6);
    }
    finally
    {
      eContainer.sharedLock.unlock();
    }
  }
  /* Various tests not either clean or complete */