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

boli
03.09.2006 83dd61651cb5d73c1a15dfcb7d217c0f272722d2
Refactoring of the JEB backend to simplify the container and entryContainer abstraction. This also elimates exposing the JE interface to backendImpl by creating a new RootContainer class. It provides a higher-level interface to access raw data in JE from anywhere in the server (ie. unit tests). 

Fix for issue 727.
1 files deleted
1 files added
15 files modified
2804 ■■■■ changed files
opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java 28 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java 504 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/Config.java 7 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/Container.java 557 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/DN2ID.java 30 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/DN2URI.java 24 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java 667 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ExportJob.java 98 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ID2Entry.java 33 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ImportContext.java 20 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ImportJob.java 92 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ImportThread.java 6 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/Index.java 38 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/IndexFilter.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/RootContainer.java 622 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/VerifyJob.java 53 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestEntryContainer.java 21 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java
@@ -79,9 +79,9 @@
       new DatabaseEntry("+".getBytes());
  /**
   * The container in which this attribute index resides.
   * The entryContainer in which this attribute index resides.
   */
  Container container;
  EntryContainer entryContainer;
  /**
   * The attribute index configuration.
@@ -110,13 +110,13 @@
  /**
   * Create a new attribute index object.
   * @param entryContainer The entryContainer of this attribute index.
   * @param indexConfig The attribute index configuration.
   * @param container The container of this attribute index.
   */
  public AttributeIndex(IndexConfig indexConfig, Container container)
  public AttributeIndex(EntryContainer entryContainer, IndexConfig indexConfig)
  {
    this.entryContainer = entryContainer;
    this.indexConfig = indexConfig;
    this.container = container;
    AttributeType attrType = indexConfig.getAttributeType();
    String name = attrType.getNameOrOID();
@@ -124,7 +124,7 @@
    if (indexConfig.isEqualityIndex())
    {
      Indexer equalityIndexer = new EqualityIndexer(indexConfig);
      this.equalityIndex = new Index(container, name + ".equality",
      this.equalityIndex = new Index(this.entryContainer, name + ".equality",
                                     equalityIndexer,
                                     indexConfig.getEqualityEntryLimit(),
                                     indexConfig.getCursorEntryLimit());
@@ -133,7 +133,7 @@
    if (indexConfig.isPresenceIndex())
    {
      Indexer presenceIndexer = new PresenceIndexer(indexConfig);
      this.presenceIndex = new Index(container, name + ".presence",
      this.presenceIndex = new Index(this.entryContainer, name + ".presence",
                                     presenceIndexer,
                                     indexConfig.getPresenceEntryLimit(),
                                     indexConfig.getCursorEntryLimit());
@@ -142,7 +142,7 @@
    if (indexConfig.isSubstringIndex())
    {
      Indexer substringIndexer = new SubstringIndexer(indexConfig);
      this.substringIndex = new Index(container, name + ".substring",
      this.substringIndex = new Index(this.entryContainer, name + ".substring",
                                     substringIndexer,
                                     indexConfig.getSubstringEntryLimit(),
                                     indexConfig.getCursorEntryLimit());
@@ -151,7 +151,7 @@
    if (indexConfig.isOrderingIndex())
    {
      Indexer orderingIndexer = new OrderingIndexer(indexConfig);
      this.orderingIndex = new Index(container, name + ".ordering",
      this.orderingIndex = new Index(this.entryContainer, name + ".ordering",
                                     orderingIndexer,
                                     indexConfig.getEqualityEntryLimit(),
                                     indexConfig.getCursorEntryLimit());
@@ -195,7 +195,7 @@
   */
  public void close()
  {
    // The container is responsible for closing the JE databases.
    // The entryContainer is responsible for closing the JE databases.
  }
  /**
@@ -791,19 +791,19 @@
    String name = attrType.getNameOrOID();
    if (indexConfig.isEqualityIndex())
    {
      container.removeDatabase(name + ".equality");
      entryContainer.removeDatabase(name + ".equality");
    }
    if (indexConfig.isPresenceIndex())
    {
      container.removeDatabase(name + ".presence");
      entryContainer.removeDatabase(name + ".presence");
    }
    if (indexConfig.isSubstringIndex())
    {
      container.removeDatabase(name + ".substring");
      entryContainer.removeDatabase(name + ".substring");
    }
    if (indexConfig.isOrderingIndex())
    {
      container.removeDatabase(name + ".ordering");
      entryContainer.removeDatabase(name + ".ordering");
    }
  }
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -26,29 +26,13 @@
 */
package org.opends.server.backends.jeb;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import com.sleepycat.je.CheckpointConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentStats;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.PreloadConfig;
import com.sleepycat.je.PreloadStats;
import com.sleepycat.je.PreloadStatus;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.config.ConfigParam;
import org.opends.server.api.Backend;
import org.opends.server.api.ConfigurableComponent;
@@ -67,8 +51,6 @@
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.CancelledOperationException;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DebugLogCategory;
import org.opends.server.types.DebugLogSeverity;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
@@ -79,10 +61,7 @@
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.ResultCode;
import org.opends.server.types.FilePermission;
import org.opends.server.monitors.DatabaseEnvironmentMonitor;
import org.opends.server.util.LDIFException;
import org.opends.server.loggers.Debug;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.JebMessages.*;
@@ -117,26 +96,9 @@
  private Config config;
  /**
   * The directory containing persistent storage for the backend.
   * The root JE container to use for this backend.
   */
  private File backendDirectory;
  /**
   * The permissions mode for the directory containing persistent storage for
   * the backend.
   */
  private FilePermission backendPermission;
  /**
   * The base DNs contained in this backend.
   */
  private ConcurrentHashMap<DN, EntryContainer> baseDNs;
  /**
   * The JE environment for this backend instance.  Each instance has its own
   * environment.
   */
  private com.sleepycat.je.Environment dbEnv;
  private RootContainer rootContainer;
  /**
   * A configurable component to handle changes to the configuration of
@@ -273,46 +235,6 @@
    }
  }
  /**
   * Determine the container of an entry DN.
   *
   * @param dn The DN of an entry.
   * @return The container of the entry.
   * @throws DirectoryException If the entry DN does not match any of the base
   *                            DNs.
   */
  private EntryContainer getContainer(DN dn) throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "getContainer");
    EntryContainer ec = null;
    DN nodeDN = dn;
    while (ec == null && nodeDN != null)
    {
      ec = baseDNs.get(nodeDN);
      if (ec == null)
      {
        nodeDN = nodeDN.getParent();
      }
    }
    if (ec == null)
    {
      // The operation should not have been routed to this backend.
      String message = getMessage(MSGID_JEB_INCORRECT_ROUTING,
                                  dn.toString());
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message,
                                   MSGID_JEB_INCORRECT_ROUTING);
    }
    return ec;
  }
  /**
   * This method constructs a container name from a base DN. Only alphanumeric
   * characters are preserved, all other characters are replaced with an
@@ -371,34 +293,6 @@
    config = new Config();
    config.initializeConfig(configEntry, baseDNs);
    // Get the backend database directory.
    backendDirectory = config.getBackendDirectory();
    if (!backendDirectory.isDirectory())
    {
      String message = getMessage(MSGID_JEB_DIRECTORY_INVALID,
                                  backendDirectory.getPath());
      throw new InitializationException(MSGID_JEB_DIRECTORY_INVALID,
                                        message);
    }
    // Get the backend database directory permissions and apply
    try
    {
      backendPermission = config.getBackendPermission();
      if(!FilePermission.setPermissions(backendDirectory, backendPermission))
      {
        throw new Exception();
      }
    }
    catch(Exception e)
    {
      // Log an warning that the permissions were not set.
      int msgID = MSGID_JEB_SET_PERMISSIONS_FAILED;
      String message = getMessage(msgID, backendDirectory.getPath());
      logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_WARNING,
               message, msgID);
    }
    // FIXME: Currently assuming every base DN is also a suffix.
    for (DN dn : baseDNs)
    {
@@ -416,14 +310,8 @@
    // Open the database environment
    try
    {
      dbEnv = new Environment(backendDirectory,
                              config.getEnvironmentConfig());
      Debug.debugMessage(DebugLogCategory.BACKEND, DebugLogSeverity.INFO,
                         CLASS_NAME, "initializeBackend",
                         dbEnv.getConfig().toString());
//      cleanDatabase();
      rootContainer = new RootContainer(config, this);
      rootContainer.open();
    }
    catch (DatabaseException e)
    {
@@ -433,45 +321,33 @@
      throw new InitializationException(MSGID_JEB_OPEN_ENV_FAIL, message, e);
    }
    // Create and register a monitor provider for the environment.
    String monitorName = this.getBackendID() + " Database Environment";
    // Register a monitor provider for the environment.
    MonitorProvider monitorProvider =
         new DatabaseEnvironmentMonitor(monitorName, dbEnv);
        rootContainer.getMonitorProvider();
    monitorProviders.add(monitorProvider);
    DirectoryServer.registerMonitorProvider(monitorProvider);
    // Open all the databases.
    this.baseDNs = new ConcurrentHashMap<DN, EntryContainer>(baseDNs.length);
    for (DN dn : baseDNs)
    try
    {
      String containerName = getContainerName(dn);
      Container container = new Container(dbEnv, containerName);
      EntryContainer ec = new EntryContainer(this, config, container);
      try
      {
        ec.open();
      }
      catch (DatabaseException databaseException)
      {
        assert debugException(CLASS_NAME, "initializeBackend",
                              databaseException);
        String message = getMessage(MSGID_JEB_OPEN_DATABASE_FAIL,
                                    databaseException.getMessage());
        throw new InitializationException(MSGID_JEB_OPEN_DATABASE_FAIL, message,
                                          databaseException);
      }
      this.baseDNs.put(dn, ec);
      rootContainer.openEntryContainers(baseDNs);
    }
    catch (DatabaseException databaseException)
    {
      assert debugException(CLASS_NAME, "initializeBackend",
                            databaseException);
      String message = getMessage(MSGID_JEB_OPEN_DATABASE_FAIL,
                                  databaseException.getMessage());
      throw new InitializationException(MSGID_JEB_OPEN_DATABASE_FAIL, message,
                                        databaseException);
    }
    // Preload the database cache.
    preload();
    rootContainer.preload();
    // Determine the next entry ID and the total number of entries.
    EntryID highestID = null;
    long entryCount = 0;
    for (EntryContainer ec : this.baseDNs.values())
    for (EntryContainer ec : rootContainer.getEntryContainers())
    {
      try
      {
@@ -502,76 +378,14 @@
    DirectoryServer.registerConfigurableComponent(this);
    // Register the database environment as a configurable component.
    DN envConfigDN = config.getEnvConfigDN();
    if (envConfigDN != null)
    ConfigurableEnvironment configurableEnv =
        rootContainer.getConfigurableEnvironment();
    if (configurableEnv != null)
    {
      configurableEnv = new ConfigurableEnvironment(envConfigDN, dbEnv);
      DirectoryServer.registerConfigurableComponent(configurableEnv);
    }
  }
  /**
   * Synchronously invokes the cleaner on the database environment then forces a
   * checkpoint to delete the log files that are no longer in use.
   *
   * @throws DatabaseException If an error occurs while cleaning the database
   * environment.
   */
  private void cleanDatabase()
       throws DatabaseException
  {
    assert debugEnter(CLASS_NAME, "cleanDatabase");
    int msgID;
    String message;
    FilenameFilter filenameFilter = new FilenameFilter()
    {
      public boolean accept(File d, String name)
      {
        return name.endsWith(".jdb");
      }
    };
    int beforeLogfileCount = backendDirectory.list(filenameFilter).length;
    msgID = MSGID_JEB_CLEAN_DATABASE_START;
    message = getMessage(msgID, beforeLogfileCount, backendDirectory.getPath());
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE, message,
             msgID);
    int currentCleaned = 0;
    int totalCleaned = 0;
    while ((currentCleaned = dbEnv.cleanLog()) > 0)
    {
      totalCleaned += currentCleaned;
    }
    msgID = MSGID_JEB_CLEAN_DATABASE_MARKED;
    message = getMessage(msgID, totalCleaned);
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE, message,
             msgID);
    if (totalCleaned > 0)
    {
      CheckpointConfig force = new CheckpointConfig();
      force.setForce(true);
      dbEnv.checkpoint(force);
    }
    int afterLogfileCount = backendDirectory.list(filenameFilter).length;
    msgID = MSGID_JEB_CLEAN_DATABASE_FINISH;
    message = getMessage(msgID, afterLogfileCount);
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE, message,
             msgID);
  }
  /**
   * Performs any necessary work to finalize this backend, including closing any
   * underlying databases or connections and deregistering any suffixes that it
@@ -595,7 +409,7 @@
    // Deregister our suffixes.
    // FIXME: Currently assuming every base DN is also a suffix.
    for (DN dn : baseDNs.keySet())
    for (DN dn : rootContainer.getBaseDNs())
    {
      try
      {
@@ -623,11 +437,7 @@
    // Close the database.
    try
    {
      for (EntryContainer ec : baseDNs.values())
      {
        ec.close();
      }
      dbEnv.close();
      rootContainer.close();
    }
    catch (DatabaseException e)
    {
@@ -850,7 +660,7 @@
    readerBegin();
    try
    {
      EntryContainer ec = getContainer(entryDN);
      EntryContainer ec = rootContainer.getEntryContainer(entryDN);
      Entry entry;
      try
      {
@@ -903,7 +713,7 @@
    try
    {
      DN entryDN = entry.getDN();
      EntryContainer ec = getContainer(entryDN);
      EntryContainer ec = rootContainer.getEntryContainer(entryDN);
      try
      {
@@ -954,7 +764,7 @@
    writerBegin();
    try
    {
      EntryContainer ec = getContainer(entryDN);
      EntryContainer ec = rootContainer.getEntryContainer(entryDN);
      try
      {
        ec.deleteEntry(entryDN, deleteOperation);
@@ -1005,7 +815,7 @@
    try
    {
      DN entryDN = entry.getDN();
      EntryContainer ec = getContainer(entryDN);
      EntryContainer ec = rootContainer.getEntryContainer(entryDN);
      try
      {
@@ -1061,12 +871,13 @@
    writerBegin();
    try
    {
      EntryContainer currentContainer = getContainer(currentDN);
      EntryContainer container = getContainer(entry.getDN());
      EntryContainer currentContainer = rootContainer.getEntryContainer(
          currentDN);
      EntryContainer container = rootContainer.getEntryContainer(entry.getDN());
      if (currentContainer != container)
      {
        // No reason why we cannot implement a move between containers
        // 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);
@@ -1115,7 +926,8 @@
    readerBegin();
    try
    {
      EntryContainer ec = getContainer(searchOperation.getBaseDN());
      EntryContainer ec = rootContainer.getEntryContainer(
          searchOperation.getBaseDN());
      ec.search(searchOperation);
    }
    catch (DatabaseException e)
@@ -1166,48 +978,24 @@
                                   e.getMessageID());
    }
    // If the backend already has the environment open, we must use the same
    // underlying environment instance and its configuration.
    Environment env;
    try
    {
      EnvironmentConfig envConfig;
      Environment existingEnv = dbEnv;
      if (existingEnv == null)
      {
        // Open the environment read-only.
        envConfig = config.getEnvironmentConfig();
        envConfig.setReadOnly(true);
        envConfig.setAllowCreate(false);
        envConfig.setTransactional(false);
      }
      else
      {
        envConfig = existingEnv.getConfig();
      }
      // Open a new environment handle.
      File backendDirectory = config.getBackendDirectory();
      env = new Environment(backendDirectory, envConfig);
      Debug.debugMessage(DebugLogCategory.BACKEND, DebugLogSeverity.INFO,
                         CLASS_NAME, "exportLDIF",
                         env.getConfig().toString());
    }
    catch (DatabaseException e)
    {
      assert debugException(CLASS_NAME, "exportLDIF", e);
      String message = getMessage(MSGID_JEB_DATABASE_EXCEPTION,
                                  e.getMessage());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, MSGID_JEB_DATABASE_EXCEPTION);
    }
    // If the backend already has the root container open, we must use the same
    // underlying root container
    boolean openRootContainer = rootContainer == null;
    try
    {
      ExportJob exportJob = new ExportJob(this, config, exportConfig);
      exportJob.exportLDIF(env);
      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(baseDNs);
      }
      ExportJob exportJob = new ExportJob(exportConfig);
      exportJob.exportLDIF(rootContainer);
    }
    catch (IOException ioe)
    {
@@ -1241,13 +1029,19 @@
    }
    finally
    {
      try
      //If a root container was opened in this method as read only, close it
      //to leave the backend in the same state.
      if (openRootContainer && rootContainer != null)
      {
        env.close();
      }
      catch (DatabaseException e)
      {
        assert debugException(CLASS_NAME, "exportLDIF", e);
        try
        {
          rootContainer.close();
          rootContainer = null;
        }
        catch (DatabaseException e)
        {
          assert debugException(CLASS_NAME, "exportLDIF", e);
        }
      }
    }
  }
@@ -1342,10 +1136,24 @@
    config = new Config();
    config.initializeConfig(configEntry, baseDNs);
    VerifyJob verifyJob = new VerifyJob(this, config, verifyConfig);
    // If the backend already has the root container open, we must use the same
    // underlying root container
    boolean openRootContainer = rootContainer == null;
    try
    {
      verifyJob.verifyBackend();
      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(baseDNs);
      }
      VerifyJob verifyJob = new VerifyJob(config, verifyConfig);
      verifyJob.verifyBackend(rootContainer);
    }
    catch (DatabaseException e)
    {
@@ -1362,6 +1170,23 @@
                                   e.getMessage(),
                                   e.getMessageID());
    }
    finally
    {
      //If a root container was opened in this method as read only, close it
      //to leave the backend in the same state.
      if (openRootContainer && rootContainer != null)
      {
        try
        {
          rootContainer.close();
          rootContainer = null;
        }
        catch (DatabaseException e)
        {
          assert debugException(CLASS_NAME, "verifyBackend", e);
        }
      }
    }
  }
@@ -1582,78 +1407,22 @@
          // operation threads with a handle on the entry container being
          // closed.
          DirectoryServer.deregisterSuffix(baseDN);
          EntryContainer ec = this.baseDNs.remove(baseDN);
          ec.close();
          ec.removeContainer();
          rootContainer.removeEntryContainer(baseDN);
        }
      }
      for (DN baseDN : newConfig.getBaseDNs())
      {
        if (!this.baseDNs.containsKey(baseDN))
        if (!rootContainer.getBaseDNs().contains(baseDN))
        {
          // The base DN was added.
          String containerName = getContainerName(baseDN);
          Container container = new Container(dbEnv, containerName);
          EntryContainer ec = new EntryContainer(this, config, container);
          ec.open();
          this.baseDNs.put(baseDN, ec);
          rootContainer.openEntryContainer(baseDN);
          DirectoryServer.registerSuffix(baseDN, this);
        }
      }
      // Check for changes to the database directory permissions
      FilePermission oldPermission = config.getBackendPermission();
      FilePermission newPermission = newConfig.getBackendPermission();
      if(!FilePermission.toUNIXMode(oldPermission).equals(
          FilePermission.toUNIXMode(newPermission)))
      {
        try
        {
          if(!FilePermission.setPermissions(newConfig.getBackendDirectory(),
              newPermission))
          {
            throw new Exception();
          }
        }
        catch(Exception e)
        {
          // Log an warning that the permissions were not set.
          int msgID = MSGID_JEB_SET_PERMISSIONS_FAILED;
          String message = getMessage(msgID, backendDirectory.getPath());
          logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_WARNING,
                   message, msgID);
        }
      }
      // Check if any JE non-mutable properties were changed.
      EnvironmentConfig oldEnvConfig = config.getEnvironmentConfig();
      EnvironmentConfig newEnvConfig = newConfig.getEnvironmentConfig();
      Map paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
      for (Object o : paramsMap.values())
      {
        ConfigParam param = (ConfigParam)o;
        if (!param.isMutable())
        {
          String oldValue = oldEnvConfig.getConfigParam(param.getName());
          String newValue = newEnvConfig.getConfigParam(param.getName());
          if (!oldValue.equalsIgnoreCase(newValue))
          {
            System.out.println("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.
      dbEnv.setMutableConfig(newConfig.getEnvironmentConfig());
      Debug.debugMessage(DebugLogCategory.BACKEND, DebugLogSeverity.INFO,
                         CLASS_NAME, "applyNewConfiguration",
                         dbEnv.getConfig().toString());
      // Apply new JE configuration
      rootContainer.applyNewConfig(config);
      // Put the new configuration in place.
      config = newConfig;
@@ -1671,76 +1440,15 @@
    return ccr;
  }
  /**
   * Preload the database cache. There is no preload if the configured preload
   * time limit is zero.
   * Returns a handle to the JE root container currently used by this backend.
   * The rootContainer could be NULL if the backend is not initialized.
   *
   * @return The RootContainer object currently used by this backend.
   */
  private void preload()
  public RootContainer getRootContainer()
  {
    assert debugEnter(CLASS_NAME, "preload");
    long timeLimit = config.getPreloadTimeLimit();
    if (timeLimit > 0)
    {
      // Get a list of all the databases used by the backend.
      ArrayList<Database> dbList = new ArrayList<Database>();
      for (EntryContainer ec : baseDNs.values())
      {
        ec.listDatabases(dbList);
      }
      // Sort the list in order of priority.
      Collections.sort(dbList, new DbPreloadComparator());
      // Preload each database until we reach the time limit or the cache
      // is filled.
      try
      {
        long timeEnd = System.currentTimeMillis() + timeLimit;
        // Configure preload of Leaf Nodes (LNs) containing the data values.
        PreloadConfig preloadConfig = new PreloadConfig();
        preloadConfig.setLoadLNs(true);
        for (Database db : dbList)
        {
          // Calculate the remaining time.
          long timeRemaining = timeEnd - System.currentTimeMillis();
          if (timeRemaining <= 0)
          {
            break;
          }
          preloadConfig.setMaxMillisecs(timeRemaining);
          PreloadStats preloadStats = db.preload(preloadConfig);
/*
          System.out.println("file=" + db.getDatabaseName() +
                             " LNs=" + preloadStats.getNLNsLoaded());
*/
          // Stop if the cache is full or the time limit has been exceeded.
          if (preloadStats.getStatus() != PreloadStatus.SUCCESS)
          {
            break;
          }
        }
        // Log an informational message about the size of the cache.
        EnvironmentStats stats = dbEnv.getStats(new StatsConfig());
        long total = stats.getCacheTotalBytes();
        int msgID = MSGID_JEB_CACHE_SIZE_AFTER_PRELOAD;
        String message = getMessage(msgID, total / (1024 * 1024));
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE, message,
                 msgID);
      }
      catch (DatabaseException e)
      {
        assert debugException(CLASS_NAME, "preload", e);
      }
    }
    assert debugEnter(CLASS_NAME, "getRootContainer");
    return rootContainer;
  }
}
opends/src/server/org/opends/server/backends/jeb/Config.java
@@ -356,6 +356,13 @@
      throw new ConfigException(msgID, message);
    }
    backendDirectory = getFileForPath(backendDirectoryAttr.activeValue());
    //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);
    }
    // ds-cfg-backend-mode
    // Optional, single-valued config attribute requiring admin action on change
opends/src/server/org/opends/server/backends/jeb/Container.java
File was deleted
opends/src/server/org/opends/server/backends/jeb/DN2ID.java
@@ -49,9 +49,9 @@
public class DN2ID
{
  /**
   * The database container.
   * The database entryContainer.
   */
  private Container container;
  private EntryContainer entryContainer;
  /**
   * The JE database configuration.
@@ -59,7 +59,7 @@
  private DatabaseConfig dbConfig;
  /**
   * The name of the database within the container.
   * The name of the database within the entryContainer.
   */
  private String name;
@@ -70,16 +70,17 @@
       new ThreadLocal<Database>();
  /**
   * Create a DN2ID instance for the DN database in a given container.
   * Create a DN2ID instance for the DN database in a given entryContainer.
   *
   * @param container The container of the DN database.
   * @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(Container container, DatabaseConfig dbConfig, String name)
  public DN2ID(EntryContainer entryContainer, DatabaseConfig dbConfig,
               String name)
  {
    this.container = container;
    this.entryContainer = entryContainer;
    this.dbConfig = dbConfig;
    this.name = name;
  }
@@ -96,7 +97,7 @@
  /**
   * Get a handle to the database. It returns a per-thread handle to avoid
   * any thread contention on the database handle. The container is
   * any thread contention on the database handle. The entryContainer is
   * responsible for closing all handles.
   *
   * @return A database handle.
@@ -108,7 +109,7 @@
    Database database = threadLocalDatabase.get();
    if (database == null)
    {
      database = container.openDatabase(dbConfig, name);
      database = entryContainer.openDatabase(dbConfig, name);
      threadLocalDatabase.set(database);
    }
    return database;
@@ -144,7 +145,7 @@
    OperationStatus status;
    status = Container.insert(getDatabase(), txn, key, data);
    status = EntryContainer.insert(getDatabase(), txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -171,7 +172,7 @@
    DatabaseEntry data = id.getDatabaseEntry();
    OperationStatus status;
    status = Container.put(getDatabase(), txn, key, data);
    status = EntryContainer.put(getDatabase(), txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -194,7 +195,7 @@
       throws DatabaseException
  {
    OperationStatus status;
    status = Container.put(getDatabase(), txn, key, data);
    status = EntryContainer.put(getDatabase(), txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -216,7 +217,7 @@
  {
    DatabaseEntry key = DNdata(dn);
    OperationStatus status = Container.delete(getDatabase(), txn, key);
    OperationStatus status = EntryContainer.delete(getDatabase(), txn, key);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -239,7 +240,8 @@
    DatabaseEntry data = new DatabaseEntry();
    OperationStatus status;
    status = Container.read(getDatabase(), txn, key, data, LockMode.DEFAULT);
    status = EntryContainer.read(getDatabase(), txn, key, data,
                                 LockMode.DEFAULT);
    if (status != OperationStatus.SUCCESS)
    {
      return null;
opends/src/server/org/opends/server/backends/jeb/DN2URI.java
@@ -90,9 +90,9 @@
       DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
  /**
   * The database container.
   * The database entryContainer.
   */
  private Container container;
  private EntryContainer entryContainer;
  /**
   * The JE database configuration.
@@ -100,7 +100,7 @@
  private DatabaseConfig dbConfig;
  /**
   * The name of the database within the container.
   * The name of the database within the entryContainer.
   */
  private String name;
@@ -116,16 +116,18 @@
       new ThreadLocal<Database>();
  /**
   * Create a new object representing a referral database in a given container.
   * Create a new object representing a referral database in a given
   * entryContainer.
   *
   * @param container The container of the referral database.
   * @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(Container container, DatabaseConfig dbConfig, String name)
  public DN2URI(EntryContainer entryContainer, DatabaseConfig dbConfig,
                String name)
  {
    this.container = container;
    this.entryContainer = entryContainer;
    this.dbConfig = dbConfig;
    this.name = name;
  }
@@ -142,7 +144,7 @@
  /**
   * Get a handle to the database. It returns a per-thread handle to avoid
   * any thread contention on the database handle. The container is
   * any thread contention on the database handle. The entryContainer is
   * responsible for closing all handles.
   *
   * @return A database handle.
@@ -154,7 +156,7 @@
    Database database = threadLocalDatabase.get();
    if (database == null)
    {
      database = container.openDatabase(dbConfig, name);
      database = entryContainer.openDatabase(dbConfig, name);
      threadLocalDatabase.set(database);
    }
    return database;
@@ -181,7 +183,7 @@
    // The JE insert method does not permit duplicate keys so we must use the
    // put method.
    status = Container.put(getDatabase(), txn, key, data);
    status = EntryContainer.put(getDatabase(), txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -206,7 +208,7 @@
    DatabaseEntry key = new DatabaseEntry(normDN);
    OperationStatus status;
    status = Container.delete(getDatabase(), txn, key);
    status = EntryContainer.delete(getDatabase(), txn, key);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -26,16 +26,7 @@
 */
package org.opends.server.backends.jeb;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DatabaseNotFoundException;
import com.sleepycat.je.DeadlockException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.*;
import org.opends.server.api.AttributeSyntax;
import org.opends.server.api.Backend;
@@ -47,6 +38,7 @@
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.Operation;
import org.opends.server.core.SearchOperation;
import org.opends.server.loggers.Debug;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.ldap.LDAPException;
import org.opends.server.controls.PagedResultsControl;
@@ -55,6 +47,7 @@
import org.opends.server.types.AttributeValue;
import org.opends.server.types.CancelledOperationException;
import org.opends.server.types.Control;
import org.opends.server.types.DebugLogCategory;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
@@ -77,6 +70,10 @@
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.JebMessages.*;
import static org.opends.server.loggers.Debug.debugException;
import static org.opends.server.types.DebugLogSeverity.VERBOSE;
import static org.opends.server.types.DebugLogCategory.DATABASE_ACCESS;
import static org.opends.server.types.DebugLogCategory.DATABASE_WRITE;
import static org.opends.server.types.DebugLogCategory.DATABASE_READ;
import static org.opends.server.util.ServerConstants.OID_SUBTREE_DELETE_CONTROL;
import static org.opends.server.util.ServerConstants.OID_PAGED_RESULTS_CONTROL;
@@ -93,6 +90,16 @@
  private static final String CLASS_NAME =
       "org.opends.server.backends.jeb.EntryContainer";
 /**
   * The JE database environment.
   */
  private static Environment env;
  /**
   * The backend configuration.
   */
  private static Config config;
  /**
   * The name of the entry database.
   */
@@ -124,19 +131,26 @@
  public static final String ATTR_DEBUG_SEARCH_INDEX = "debugsearchindex";
  /**
   * The backend to which this entry container belongs.
   * The backend to which this entry entryContainer belongs.
   */
  private Backend backend;
  /**
   * The database container.
   * The baseDN this entry container is responsible for.
   */
  private Container container;
  private DN baseDN;
  /**
   * The backend configuration.
   * A list of JE database handles opened through this entryContainer.
   * They will be closed by the entryContainer.
   */
  private Config config;
  private ArrayList<Database> databases;
  /**
   * A list of JE cursor handles registered with this entryContainer.
   * They will be closed by the entryContainer.
   */
  private ArrayList<Cursor> cursors;
  /**
   * The DN database maps a normalized DN string to an entry ID (8 bytes).
@@ -179,25 +193,34 @@
  private HashMap<AttributeType, AttributeIndex> attrIndexMap;
  /**
   * Create a new entry container object.  This method does not actually create
   * anything in the JE database environment.
   * Create a new entry entryContainer object.
   *
   * @param baseDN  The baseDN this entry container will be responsible for
   *                storing on disk.
   * @param backend A reference to the JE backend that is creating this entry
   * container.  It is needed by the Directory Server entry cache methods.
   *                container. It is needed by the Directory Server entry cache
   *                methods.
   * @param config The configuration of the JE backend.
   * @param container The databases reside in this container.
   * @param env The JE environment to create this entryContainer in
   */
  public EntryContainer(Backend backend, Config config, Container container)
  public EntryContainer(DN baseDN, Backend backend, Config config,
                        Environment env)
  {
    this.backend = backend;
    this.baseDN = baseDN;
    this.config = config;
    this.container = container;
    this.env = env;
    // Instantiate database and cursor lists
    databases = new ArrayList<Database>();
    cursors = new ArrayList<Cursor>();
    // Instantiate indexes for id2children and id2subtree.
    id2children = new Index(container, ID2CHILDREN_DATABASE_NAME,
    id2children = new Index(this, ID2CHILDREN_DATABASE_NAME,
                            new ID2CIndexer(),
                            config.getBackendIndexEntryLimit(),
                            0);
    id2subtree = new Index(container, ID2SUBTREE_DATABASE_NAME,
    id2subtree = new Index(this, ID2SUBTREE_DATABASE_NAME,
                           new ID2SIndexer(),
                           config.getBackendIndexEntryLimit(),
                           0);
@@ -208,7 +231,7 @@
    {
      for (IndexConfig indexConfig : config.getIndexConfigMap().values())
      {
        AttributeIndex index = new AttributeIndex(indexConfig, container);
        AttributeIndex index = new AttributeIndex(this, indexConfig);
        attrIndexMap.put(indexConfig.getAttributeType(), index);
      }
    }
@@ -218,7 +241,7 @@
  }
  /**
   * Opens the container for reading and writing.
   * Opens the entryContainer for reading and writing.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
@@ -229,10 +252,10 @@
    DatabaseConfig dbNodupsConfig = new DatabaseConfig();
    dbNodupsConfig.setAllowCreate(true);
    dbNodupsConfig.setTransactional(true);
    container.open();
    try
    {
      id2entry = new ID2Entry(container, dbNodupsConfig, entryDataConfig,
      id2entry = new ID2Entry(this, dbNodupsConfig, entryDataConfig,
                              ID2ENTRY_DATABASE_NAME);
      id2entry.open();
@@ -242,7 +265,7 @@
      dn2idConfig.setAllowCreate(true);
      dn2idConfig.setTransactional(true);
      dn2idConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2id = new DN2ID(container, dn2idConfig, DN2ID_DATABASE_NAME);
      dn2id = new DN2ID(this, dn2idConfig, DN2ID_DATABASE_NAME);
      dn2id.open();
      id2children.open(dbNodupsConfig);
@@ -253,7 +276,7 @@
      dn2uriConfig.setAllowCreate(true);
      dn2uriConfig.setTransactional(true);
      dn2uriConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2uri = new DN2URI(container, dn2uriConfig, REFERRAL_DATABASE_NAME);
      dn2uri = new DN2URI(this, dn2uriConfig, REFERRAL_DATABASE_NAME);
      dn2uri.open();
      for (AttributeIndex index : attrIndexMap.values())
@@ -264,16 +287,16 @@
    catch (DatabaseException e)
    {
      assert debugException(CLASS_NAME, "open", e);
      container.close();
      close();
      throw e;
    }
  }
  /**
   * Opens the container for reading and writing without transactions.
   * Opens the entryContainer for reading and writing without transactions.
   *
   * @param  deferredWrite  Indicates whether to open the container using the
   *                        deferred write mode.
   * @param  deferredWrite  Indicates whether to open the entryContainer using
   *                        the deferred write mode.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
@@ -286,10 +309,9 @@
    dbNodupsConfig.setTransactional(false);
    dbNodupsConfig.setDeferredWrite(deferredWrite);
    container.open();
    try
    {
      id2entry = new ID2Entry(container, dbNodupsConfig, entryDataConfig,
      id2entry = new ID2Entry(this, dbNodupsConfig, entryDataConfig,
                              ID2ENTRY_DATABASE_NAME);
      id2entry.open();
@@ -300,7 +322,7 @@
      dn2idConfig.setTransactional(false);
      dn2idConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2idConfig.setDeferredWrite(deferredWrite);
      dn2id = new DN2ID(container, dn2idConfig, DN2ID_DATABASE_NAME);
      dn2id = new DN2ID(this, dn2idConfig, DN2ID_DATABASE_NAME);
      dn2id.open();
      id2children.open(dbNodupsConfig);
@@ -312,7 +334,7 @@
      dn2uriConfig.setTransactional(false);
      dn2uriConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2uriConfig.setDeferredWrite(deferredWrite);
      dn2uri = new DN2URI(container, dn2uriConfig, REFERRAL_DATABASE_NAME);
      dn2uri = new DN2URI(this, dn2uriConfig, REFERRAL_DATABASE_NAME);
      dn2uri.open();
      for (AttributeIndex index : attrIndexMap.values())
@@ -323,13 +345,13 @@
    catch (DatabaseException e)
    {
      assert debugException(CLASS_NAME, "open", e);
      container.close();
      close();
      throw e;
    }
  }
  /**
   * Opens the container for reading only.
   * Opens the entryContainer for reading only.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
@@ -342,10 +364,9 @@
    dbNodupsConfig.setAllowCreate(false);
    dbNodupsConfig.setTransactional(false);
    container.open();
    try
    {
      id2entry = new ID2Entry(container, dbNodupsConfig, entryDataConfig,
      id2entry = new ID2Entry(this, dbNodupsConfig, entryDataConfig,
                              ID2ENTRY_DATABASE_NAME);
      id2entry.open();
@@ -356,7 +377,7 @@
      dn2idConfig.setAllowCreate(false);
      dn2idConfig.setTransactional(false);
      dn2idConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2id = new DN2ID(container, dn2idConfig, DN2ID_DATABASE_NAME);
      dn2id = new DN2ID(this, dn2idConfig, DN2ID_DATABASE_NAME);
      dn2id.open();
      id2children.open(dbNodupsConfig);
@@ -368,7 +389,7 @@
      dn2uriConfig.setAllowCreate(false);
      dn2uriConfig.setTransactional(false);
      dn2uriConfig.setBtreeComparator(dn2idComparator.getClass());
      dn2uri = new DN2URI(container, dn2uriConfig, REFERRAL_DATABASE_NAME);
      dn2uri = new DN2URI(this, dn2uriConfig, REFERRAL_DATABASE_NAME);
      dn2uri.open();
      for (AttributeIndex index : attrIndexMap.values())
@@ -379,21 +400,36 @@
    catch (DatabaseException e)
    {
      assert debugException(CLASS_NAME, "openReadOnly", e);
      container.close();
      close();
      throw e;
    }
  }
  /**
   * Closes the entry container.
   * Closes the entry entryContainer.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void close()
       throws DatabaseException
  {
    // The database container is responsible for closing the JE databases.
    container.close();
    // Close each cursor that has been registered.
    for (Cursor cursor : cursors)
    {
      cursor.close();
    }
    // Close each database handle that has been opened.
    for (Database database : databases)
    {
      if (database.getConfig().getDeferredWrite())
      {
        database.sync();
      }
      database.close();
    }
    for (AttributeIndex index : attrIndexMap.values())
    {
      index.close();
@@ -401,8 +437,8 @@
  }
  /**
   * Get the DN database used by this entry container. The container must
   * have been opened.
   * Get the DN database used by this entry entryContainer. The entryContainer
   * must have been opened.
   *
   * @return The DN database.
   */
@@ -412,8 +448,8 @@
  }
  /**
   * Get the entry database used by this entry container. The container must
   * have been opened.
   * Get the entry database used by this entry entryContainer. The
   * entryContainer must have been opened.
   *
   * @return The entry database.
   */
@@ -423,8 +459,8 @@
  }
  /**
   * Get the referral database used by this entry container. The container must
   * have been opened.
   * Get the referral database used by this entry entryContainer. The
   * entryContainer must have been opened.
   *
   * @return The referral database.
   */
@@ -434,8 +470,8 @@
  }
  /**
   * Get the children database used by this entry container.
   * The container must have been opened.
   * Get the children database used by this entry entryContainer.
   * The entryContainer must have been opened.
   *
   * @return The children database.
   */
@@ -445,8 +481,8 @@
  }
  /**
   * Get the subtree database used by this entry container.
   * The container must have been opened.
   * Get the subtree database used by this entry entryContainer.
   * The entryContainer must have been opened.
   *
   * @return The subtree database.
   */
@@ -467,8 +503,8 @@
  }
  /**
   * Determine the highest entryID in the container.
   * The container must already be open.
   * Determine the highest entryID in the entryContainer.
   * The entryContainer must already be open.
   *
   * @return The highest entry ID.
   * @throws JebException If an error occurs in the JE backend.
@@ -499,7 +535,7 @@
  }
  /**
   * Processes the specified search in this container.
   * Processes the specified search in this entryContainer.
   * Matching entries should be provided back to the core server using the
   * <CODE>SearchOperation.returnEntry</CODE> method.
   *
@@ -1269,12 +1305,12 @@
        operation.invokeOperation(txn);
        // Commit the transaction.
        Container.transactionCommit(txn);
        EntryContainer.transactionCommit(txn);
        completed = true;
      }
      catch (DeadlockException deadlockException)
      {
        Container.transactionAbort(txn);
        EntryContainer.transactionAbort(txn);
        if (retryRemaining-- <= 0)
        {
          throw deadlockException;
@@ -1284,22 +1320,22 @@
      }
      catch (DatabaseException databaseException)
      {
        Container.transactionAbort(txn);
        EntryContainer.transactionAbort(txn);
        throw databaseException;
      }
      catch (DirectoryException directoryException)
      {
        Container.transactionAbort(txn);
        EntryContainer.transactionAbort(txn);
        throw directoryException;
      }
      catch (JebException jebException)
      {
        Container.transactionAbort(txn);
        EntryContainer.transactionAbort(txn);
        throw jebException;
      }
      catch (Exception e)
      {
        Container.transactionAbort(txn);
        EntryContainer.transactionAbort(txn);
        int messageID = MSGID_JEB_UNCHECKED_EXCEPTION;
        String message = getMessage(messageID);
@@ -1378,7 +1414,7 @@
     */
    public Transaction beginTransaction() throws DatabaseException
    {
      return container.beginTransaction();
      return EntryContainer.beginTransaction();
    }
    /**
@@ -1827,7 +1863,7 @@
     */
    public Transaction beginTransaction() throws DatabaseException
    {
      return container.beginTransaction();
      return EntryContainer.beginTransaction();
    }
    /**
@@ -2356,7 +2392,7 @@
     */
    public Transaction beginTransaction() throws DatabaseException
    {
      return container.beginTransaction();
      return EntryContainer.beginTransaction();
    }
    /**
@@ -2718,7 +2754,7 @@
     */
    public Transaction beginTransaction() throws DatabaseException
    {
      return container.beginTransaction();
      return EntryContainer.beginTransaction();
    }
    /**
@@ -3196,9 +3232,9 @@
  }
  /**
   * Get a count of the number of entries stored in this entry container.
   * Get a count of the number of entries stored in this entry entryContainer.
   *
   * @return The number of entries stored in this entry container.
   * @return The number of entries stored in this entry entryContainer.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public long getEntryCount() throws DatabaseException
@@ -3207,7 +3243,8 @@
  }
  /**
   * Remove the entry container from disk. The container must not be open.
   * Remove the entry entryContainer from disk. The entryContainer must not be
   * open.
   *
   * @throws DatabaseException If an error occurs in the JE database.
   */
@@ -3215,7 +3252,7 @@
  {
    try
    {
      container.removeDatabase(DN2ID_DATABASE_NAME);
      removeDatabase(DN2ID_DATABASE_NAME);
    }
    catch (DatabaseNotFoundException e)
    {
@@ -3223,7 +3260,7 @@
    }
    try
    {
      container.removeDatabase(ID2ENTRY_DATABASE_NAME);
      removeDatabase(ID2ENTRY_DATABASE_NAME);
    }
    catch (DatabaseNotFoundException e)
    {
@@ -3231,7 +3268,7 @@
    }
    try
    {
      container.removeDatabase(ID2CHILDREN_DATABASE_NAME);
      removeDatabase(ID2CHILDREN_DATABASE_NAME);
    }
    catch (DatabaseNotFoundException e)
    {
@@ -3239,7 +3276,7 @@
    }
    try
    {
      container.removeDatabase(ID2SUBTREE_DATABASE_NAME);
      removeDatabase(ID2SUBTREE_DATABASE_NAME);
    }
    catch (DatabaseNotFoundException e)
    {
@@ -3260,7 +3297,7 @@
  /**
   * Get the number of values for which the entry limit has been exceeded
   * since the entry container was opened.
   * since the entry entryContainer was opened.
   * @return The number of values for which the entry limit has been exceeded.
   */
  public int getEntryLimitExceededCount()
@@ -3276,53 +3313,18 @@
  }
  /**
   * Begin a leaf transaction.
   * @return A JE transaction handle.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  protected Transaction beginTransaction()
       throws DatabaseException
  {
    return container.beginTransaction();
  }
  /**
   * Commit a transaction.
   * @param txn The JE transaction handle.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  protected void transactionCommit(Transaction txn)
       throws DatabaseException
  {
    Container.transactionCommit(txn);
  }
  /**
   * Abort a transaction.
   * @param txn The JE transaction handle.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  protected void transactionAbort(Transaction txn)
       throws DatabaseException
  {
    Container.transactionAbort(txn);
  }
  /**
   * Get a list of the databases opened by this container.  There will be
   * 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.
   */
  public void listDatabases(List<Database> dbList)
  {
    // The container has a list of all handles opened.
    List<Database> dbCompleteList = container.getDatabaseList();
    // 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 : dbCompleteList)
    for (Database db : databases)
    {
      try
      {
@@ -3362,4 +3364,437 @@
    return false;
  }
}
  /**
   * 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(getContainerName());
    builder.append('_');
    builder.append(name);
  }
  /**
   * 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);
        assert Debug.debugMessage(DATABASE_ACCESS, VERBOSE, CLASS_NAME,
                                  "openDatabase",
                                  "open db=" + database.getDatabaseName() +
                                  " txnid=" + txn.getId());
        transactionCommit(txn);
      }
      catch (DatabaseException e)
      {
        transactionAbort(txn);
        throw e;
      }
    }
    else
    {
      database = env.openDatabase(null, fullName, dbConfig);
      assert Debug.debugMessage(DATABASE_ACCESS, VERBOSE, CLASS_NAME,
                                "openDatabase",
                                "open db=" + database.getDatabaseName() +
                                " txnid=none");
    }
    // Insert into the list of database handles.
    databases.add(database);
    return database;
  }
  /**
   * Register a cursor with the entryContainer. The entryContainer will then
   * take care of closing the cursor when the entryContainer is closed.
   *
   * @param cursor A cursor to one of the databases in the entryContainer.
   */
  public synchronized void addCursor(Cursor cursor)
  {
    cursors.add(cursor);
  }
  /**
   * Begin a leaf transaction using the default configuration.
   * Provides assertion debug logging.
   * @return A JE transaction handle.
   * @throws DatabaseException If an error occurs while attempting to begin
   * a new transaction.
   */
  public static Transaction beginTransaction()
       throws DatabaseException
  {
    Transaction parentTxn = null;
    TransactionConfig txnConfig = null;
    Transaction txn = env.beginTransaction(parentTxn, txnConfig);
    assert Debug.debugMessage(DATABASE_ACCESS, VERBOSE, CLASS_NAME,
                       "beginTransaction", "begin txnid=" + txn.getId());
    return txn;
  }
  /**
   * Commit a transaction.
   * Provides assertion debug logging.
   * @param txn The JE transaction handle.
   * @throws DatabaseException If an error occurs while attempting to commit
   * the transaction.
   */
  public static void transactionCommit(Transaction txn)
       throws DatabaseException
  {
    if (txn != null)
    {
      txn.commit();
      assert Debug.debugMessage(DATABASE_ACCESS, VERBOSE, CLASS_NAME,
                                "transactionCommit", "commit txnid=" +
                                                     txn.getId());
    }
  }
  /**
   * Abort a transaction.
   * Provides assertion debug logging.
   * @param txn The JE transaction handle.
   * @throws DatabaseException If an error occurs while attempting to abort the
   * transaction.
   */
  public static void transactionAbort(Transaction txn)
       throws DatabaseException
  {
    if (txn != null)
    {
      txn.abort();
      assert Debug.debugMessage(DATABASE_ACCESS, VERBOSE, CLASS_NAME,
                                "transactionAbort", "abort txnid=" +
                                                    txn.getId());
    }
  }
  /**
   * Debug log a read or write access to the database.
   * @param operation The operation label: "read", "put", "insert".
   * @param category The log category for raw data value logging
   * @param status The JE return status code of the operation.
   * @param database The JE database handle operated on.
   * @param txn The JE transaction handle used in the operation.
   * @param key The database key operated on.
   * @param data The database value read or written.
   * @return true so that the method can be used in an assertion
   * @throws DatabaseException If an error occurs while retrieving information
   * about the JE objects provided to the method.
   */
  private static boolean debugAccess(String operation,
                             DebugLogCategory category,
                             OperationStatus status,
                             Database database,
                             Transaction txn,
                             DatabaseEntry key, DatabaseEntry data)
       throws DatabaseException
  {
    // Build the string that is common to category DATABASE_ACCESS and
    // DATABASE_READ/DATABASE_WRITE
    StringBuilder builder = new StringBuilder();
    builder.append(operation);
    if (status == OperationStatus.SUCCESS)
    {
      builder.append(" (ok)");
    }
    else
    {
      builder.append(" (");
      builder.append(status.toString());
      builder.append(")");
    }
    builder.append(" db=");
    builder.append(database.getDatabaseName());
    if (txn != null)
    {
      builder.append(" txnid=");
      builder.append(txn.getId());
    }
    else
    {
      builder.append(" txnid=none");
    }
    Debug.debugMessage(DATABASE_ACCESS, VERBOSE, CLASS_NAME,
                       "debugAccess", builder.toString());
    // If the operation was successful we log the same common information
    // plus the key and data under category DATABASE_READ or DATABASE_WRITE
    if (status == OperationStatus.SUCCESS)
    {
      builder.append(" key:");
      builder.append(ServerConstants.EOL);
      StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 0);
      if (data != null)
      {
        builder.append("data(len=");
        builder.append(data.getSize());
        builder.append("):");
        builder.append(ServerConstants.EOL);
        StaticUtils.byteArrayToHexPlusAscii(builder, data.getData(), 0);
      }
      Debug.debugMessage(category, VERBOSE, CLASS_NAME,
                         "debugAccess", builder.toString());
/*
      if (category == DATABASE_WRITE)
      {
        System.out.println(builder.toString());
      }
*/
    }
    return true;
  }
  /**
   * 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);
    assert debugAccess("insert", DATABASE_WRITE,
                       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);
    assert debugAccess("cursorInsert", DATABASE_WRITE,
                       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);
    assert debugAccess("put", DATABASE_WRITE,
                       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);
    assert debugAccess("cursorPut", DATABASE_WRITE,
                       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);
    assert debugAccess("read", DATABASE_READ,
                       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);
    assert debugAccess("cursorRead", DATABASE_READ,
                       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);
    assert debugAccess("delete", DATABASE_WRITE,
                       status, database, txn, key, null);
    return status;
  }
  /**
   * Remove a database from disk.
   *
   * @param name The short database name, to which the entryContainer name will
   * be added.
   * @throws DatabaseException If an error occurs while attempting to delete the
   * database.
   */
  public void removeDatabase(String name) throws DatabaseException
  {
    StringBuilder builder = new StringBuilder();
    buildDatabaseName(builder, name);
    String fullName = builder.toString();
    env.removeDatabase(null, fullName);
  }
  /**
   * Remove from disk all the databases in this entryContainer.
   *
   * @throws DatabaseException If an error occurs while attempting to delete any
   * database.
   */
  private void removeAllDatabases() throws DatabaseException
  {
    for(Database database : databases)
    {
      String name = database.getDatabaseName();
      env.removeDatabase(null, name);
    }
  }
  /**
   * This method constructs a container name from a base DN. Only alphanumeric
   * characters are preserved, all other characters are replaced with an
   * underscore.
   *
   * @return The container name for the base DN.
   */
  public String getContainerName()
  {
    String normStr = baseDN.toNormalizedString();
    StringBuilder builder = new StringBuilder(normStr.length());
    for (int i = 0; i < normStr.length(); i++)
    {
      char ch = normStr.charAt(i);
      if (Character.isLetterOrDigit(ch))
      {
        builder.append(ch);
      }
      else
      {
        builder.append('_');
      }
    }
    return builder.toString();
  }
  /**
   * Get the baseDN this entry container is responsible for.
   *
   * @return The Base DN for this entry container.
   */
  public DN getBaseDN()
  {
    return baseDN;
  }
}
opends/src/server/org/opends/server/backends/jeb/ExportJob.java
@@ -30,11 +30,9 @@
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import org.opends.server.api.Backend;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ErrorLogCategory;
@@ -44,10 +42,7 @@
import org.opends.server.util.StaticUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
import static org.opends.server.loggers.Debug.debugException;
import static org.opends.server.messages.MessageHandler.getMessage;
@@ -71,16 +66,6 @@
  private LDIFExportConfig exportConfig;
  /**
   * The JE backend instance to be exported.
   */
  private Backend backend;
  /**
   * The configuration of the JE backend instance.
   */
  private Config config;
  /**
   * The number of milliseconds between job progress reports.
   */
  private long progressInterval = 10000;
@@ -98,43 +83,38 @@
  /**
   * Create a new export job.
   *
   * @param backend The JE backend performing the export job.
   * @param config The JE backend configuration.
   * @param exportConfig The requested LDIF export configuration.
   */
  public ExportJob(Backend backend, Config config,
                   LDIFExportConfig exportConfig)
  public ExportJob(LDIFExportConfig exportConfig)
  {
    this.exportConfig = exportConfig;
    this.backend = backend;
    this.config = config;
  }
  /**
   * Export entries from the backend to an LDIF file.
   * @param env A handle to the JE database environment of the backend.
   * @param rootContainer The root container to export.
   * @throws DatabaseException If an error occurs in the JE database.
   * @throws IOException If an I/O error occurs while writing an entry.
   * @throws JebException If an error occurs in the JE backend.
   * @throws LDIFException If an error occurs while trying to determine whether
   * to write an entry.
   */
  public void exportLDIF(Environment env)
  public void exportLDIF(RootContainer rootContainer)
       throws IOException, LDIFException, DatabaseException, JebException
  {
    // Open the containers read-only.
    List<DN> includeBranches = exportConfig.getIncludeBranches();
    DN baseDNs[] = config.getBaseDNs();
    ArrayList<EntryContainer> containers =
         new ArrayList<EntryContainer>(baseDNs.length);
    for (DN baseDN : baseDNs)
    DN baseDN;
    ArrayList<EntryContainer> exportContainers =
        new ArrayList<EntryContainer>();
    for (EntryContainer entryContainer : rootContainer.getEntryContainers())
    {
      // Skip containers that are not covered by the include branches.
      baseDN = entryContainer.getBaseDN();
      boolean includeBase = false;
      if (includeBranches == null || includeBranches.isEmpty())
      {
        includeBase = true;
        exportContainers.add(entryContainer);
      }
      else
      {
@@ -143,62 +123,35 @@
          if (includeBranch.isDescendantOf(baseDN) ||
               includeBranch.isAncestorOf(baseDN))
          {
            includeBase = true;
            exportContainers.add(entryContainer);
          }
        }
      }
      if (includeBase)
      {
        String containerName = BackendImpl.getContainerName(baseDN);
        Container container = new Container(env, containerName);
        EntryContainer entryContainer =
             new EntryContainer(backend, config, container);
        if (env.getConfig().getReadOnly())
        {
          entryContainer.openReadOnly();
        }
        else
        {
          entryContainer.open();
        }
        containers.add(entryContainer);
      }
    }
    // 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 containers.
    try
    {
      // Start a timer for the progress report.
      Timer timer = new Timer();
      TimerTask progressTask = new ProgressTask();
      timer.scheduleAtFixedRate(progressTask, progressInterval,
                                progressInterval);
      // Iterate through the containers.
      try
      for (EntryContainer exportContainer : exportContainers)
      {
        for (EntryContainer ec : containers)
        {
          exportContainer(ec);
        }
      }
      finally
      {
        timer.cancel();
        exportContainer(exportContainer);
      }
    }
    finally
    {
      for (EntryContainer ec : containers)
      {
        ec.close();
      }
      timer.cancel();
    }
    long finishTime = System.currentTimeMillis();
    long totalTime = (finishTime - startTime);
@@ -217,9 +170,10 @@
  }
  /**
   * Export the entries in a single entry container, in other words from
   * Export the entries in a single entry entryContainer, in other words from
   * one of the base DNs.
   * @param entryContainer The entry container of those entries to be exported.
   * @param entryContainer The entry container that holds the entries to be
   *                       exported.
   * @throws DatabaseException If an error occurs in the JE database.
   * @throws IOException If an error occurs while writing an entry.
   * @throws  LDIFException  If an error occurs while trying to determine
opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
@@ -49,9 +49,9 @@
public class ID2Entry
{
  /**
   * The database container.
   * The database entryContainer.
   */
  private Container container;
  private EntryContainer entryContainer;
  /**
   * The JE database configuration.
@@ -64,7 +64,7 @@
  private DataConfig dataConfig;
  /**
   * The name of the database within the container.
   * The name of the database within the entryContainer.
   */
  private String name;
@@ -76,17 +76,17 @@
  /**
   * Create a new ID2Entry object.
   * @param container The container of the entry database.
   * @param entryContainer The entryContainer of the entry database.
   * @param dbConfig The JE database configuration to be used to open the
   * underlying JE database.
   * @param dataConfig The desired compression and encryption options for data
   * stored in the entry database.
   * @param name The name of the entry database.
   */
  public ID2Entry(Container container, DatabaseConfig dbConfig,
  public ID2Entry(EntryContainer entryContainer, DatabaseConfig dbConfig,
                  DataConfig dataConfig, String name)
  {
    this.container = container;
    this.entryContainer = entryContainer;
    this.dbConfig = dbConfig;
    this.name = name;
    this.dataConfig = dataConfig;
@@ -104,7 +104,7 @@
  /**
   * Get a handle to the database. It returns a per-thread handle to avoid
   * any thread contention on the database handle. The container is
   * any thread contention on the database handle. The entryContainer is
   * responsible for closing all handles.
   *
   * @return A database handle.
@@ -116,7 +116,7 @@
    Database database = threadLocalDatabase.get();
    if (database == null)
    {
      database = container.openDatabase(dbConfig, name);
      database = entryContainer.openDatabase(dbConfig, name);
      threadLocalDatabase.set(database);
    }
    return database;
@@ -152,7 +152,7 @@
    DatabaseEntry data = entryData(entry);
    OperationStatus status;
    status = Container.insert(getDatabase(), txn, key, data);
    status = EntryContainer.insert(getDatabase(), txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -176,7 +176,7 @@
    DatabaseEntry data = entryData(entry);
    OperationStatus status;
    status = Container.put(getDatabase(), txn, key, data);
    status = EntryContainer.put(getDatabase(), txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -197,7 +197,7 @@
       throws DatabaseException
  {
    OperationStatus status;
    status = Container.put(getDatabase(), txn, key, data);
    status = EntryContainer.put(getDatabase(), txn, key, data);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -218,7 +218,7 @@
  {
    DatabaseEntry key = id.getDatabaseEntry();
    OperationStatus status = Container.delete(getDatabase(), txn, key);
    OperationStatus status = EntryContainer.delete(getDatabase(), txn, key);
    if (status != OperationStatus.SUCCESS)
    {
      return false;
@@ -242,7 +242,8 @@
    DatabaseEntry data = new DatabaseEntry();
    OperationStatus status;
    status = Container.read(getDatabase(), txn, key, data, LockMode.DEFAULT);
    status = EntryContainer.read(getDatabase(), txn, key, data,
                                 LockMode.DEFAULT);
    if (status != OperationStatus.SUCCESS)
    {
@@ -311,7 +312,7 @@
    key = id.getDatabaseEntry();
    // Read the current count, if any.
    OperationStatus status = Container.read(getDatabase(), txn,
    OperationStatus status = EntryContainer.read(getDatabase(), txn,
                                            key, data, LockMode.DEFAULT);
    // Parse the current count.
@@ -343,7 +344,7 @@
    key = id.getDatabaseEntry();
    // Read the current count, if any.
    OperationStatus status = Container.read(getDatabase(), txn,
    OperationStatus status = EntryContainer.read(getDatabase(), txn,
                                            key, data, LockMode.RMW);
    // Parse the current count.
@@ -359,6 +360,6 @@
    // Write it.
    byte[] bytes = JebFormat.entryIDToDatabase(count);
    data.setData(bytes);
    Container.put(getDatabase(), txn, key, data);
    EntryContainer.put(getDatabase(), txn, key, data);
  }
}
opends/src/server/org/opends/server/backends/jeb/ImportContext.java
@@ -41,7 +41,7 @@
public class ImportContext
{
  /**
   * The name of the container for the destination base DN.
   * The name of the entryContainer for the destination base DN.
   */
  private String containerName;
@@ -66,7 +66,7 @@
  private LDIFReader ldifReader;
  /**
   * The entry container for the destination base DN.
   * The entry entryContainer for the destination base DN.
   */
  private EntryContainer entryContainer;
@@ -120,8 +120,8 @@
  }
  /**
   * Set the name of the container for the destination base DN.
   * @param containerName The container name.
   * Set the name of the entryContainer for the destination base DN.
   * @param containerName The entryContainer name.
   */
  public void setContainerName(String containerName)
  {
@@ -129,8 +129,8 @@
  }
  /**
   * Get the name of the container for the destination base DN.
   * @return The container name.
   * Get the name of the entryContainer for the destination base DN.
   * @return The entryContainer name.
   */
  public String getContainerName()
  {
@@ -210,8 +210,8 @@
  }
  /**
   * Set the entry container for the destination base DN.
   * @param entryContainer The entry container for the destination base DN.
   * Set the entry entryContainer for the destination base DN.
   * @param entryContainer The entry entryContainer for the destination base DN.
   */
  public void setEntryContainer(EntryContainer entryContainer)
  {
@@ -219,8 +219,8 @@
  }
  /**
   * Get the entry container for the destination base DN.
   * @return The entry container for the destination base DN.
   * Get the entry entryContainer for the destination base DN.
   * @return The entry entryContainer for the destination base DN.
   */
  public EntryContainer getEntryContainer()
  {
opends/src/server/org/opends/server/backends/jeb/ImportJob.java
@@ -27,8 +27,6 @@
package org.opends.server.backends.jeb;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentStats;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.Transaction;
@@ -89,9 +87,9 @@
  private Config config;
  /**
   * The database environment.
   * The root container used for this import job.
   */
  private Environment env;
  private RootContainer rootContainer;
  /**
   * The LDIF import configuration.
@@ -151,12 +149,9 @@
   *                      reading, or while reading from the LDIF file.
   * @throws JebException If an error occurs in the JE backend.
   */
  public void importLDIF() throws DatabaseException, IOException, JebException
  public void importLDIF()
      throws DatabaseException, IOException, JebException
  {
    File backendDirectory = config.getBackendDirectory();
    EnvironmentConfig envConfig = config.getEnvironmentConfig();
    envConfig.setConfigParam("je.env.runCheckpointer", "false");
/*
    envConfig.setConfigParam("je.env.runCleaner", "false");
    envConfig.setConfigParam("je.log.numBuffers", "2");
@@ -164,28 +159,30 @@
    envConfig.setConfigParam("je.log.totalBufferBytes", "30000000");
    envConfig.setConfigParam("je.log.fileMax", "100000000");
*/
    rootContainer = new RootContainer(config, backend);
    if (ldifImportConfig.appendToExistingData())
    {
      envConfig.setTransactional(true);
      envConfig.setTxnNoSync(true);
      rootContainer.open(config.getBackendDirectory(),
                         config.getBackendPermission(),
                         false, true, true, true, true, false);
    }
    else
    {
      envConfig.setTransactional(false);
      envConfig.setConfigParam("je.env.isLocking", "false");
      rootContainer.open(config.getBackendDirectory(),
                         config.getBackendPermission(),
                         false, true, false, false, false, false);
    }
    env = new Environment(backendDirectory, envConfig);
    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.
      env.close();
      EnvManager.removeFiles(backendDirectory.getPath());
      env = new Environment(backendDirectory, envConfig);
      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
@@ -205,49 +202,25 @@
             message, msgID);
    msgID = MSGID_JEB_IMPORT_ENVIRONMENT_CONFIG;
    message = getMessage(msgID, env.getConfig().toString());
    message = getMessage(msgID,
                         rootContainer.getEnvironmentConfig().toString());
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
             message, msgID);
    Debug.debugMessage(DebugLogCategory.BACKEND, DebugLogSeverity.INFO,
                       CLASS_NAME, "importLDIF",
                       env.getConfig().toString());
                       rootContainer.getEnvironmentConfig().toString());
    // Create and open the containers for each base DN.
    rootContainer.openEntryContainers(config.getBaseDNs());
    // Create the import contextes for each base DN.
    EntryID highestID = null;
    for (DN baseDN : config.getBaseDNs())
    DN baseDN;
    for (EntryContainer entryContainer : rootContainer.getEntryContainers())
    {
      String containerName = BackendImpl.getContainerName(baseDN);
      Container container = new Container(env, containerName);
      EntryContainer entryContainer =
           new EntryContainer(backend, config, container);
      if (ldifImportConfig.appendToExistingData())
      {
        entryContainer.open();
      }
      else
      {
        // We will need this code when the import can specify a subset
        // of the base DNs in the backend.
/*
        long t1, t2;
        String msg = String.format("Removing existing data for base DN '%s'",
                                   baseDN);
        System.out.println(msg);
        t1 = System.currentTimeMillis();
        container.removeAllDatabases();
        t2 = System.currentTimeMillis();
        msg = String.format("Data removed in %d seconds", (t2-t1)/1000);
        System.out.println(msg);
*/
        entryContainer.openNonTransactional(true);
      }
      baseDN = entryContainer.getBaseDN();
      // Keep track of the highest entry ID.
      EntryID id = entryContainer.getHighestEntryID();
@@ -263,7 +236,7 @@
      importContext.setLDIFImportConfig(this.ldifImportConfig);
      importContext.setBaseDN(baseDN);
      importContext.setContainerName(containerName);
      importContext.setContainerName(entryContainer.getContainerName());
      importContext.setEntryContainer(entryContainer);
      importContext.setBufferSize(bufferSize);
@@ -344,17 +317,13 @@
    }
    finally
    {
      for (ImportContext ic : importMap.values())
      {
        ic.getEntryContainer().close();
      }
      rootContainer.close();
      // Sync the environment to disk.
      msgID = MSGID_JEB_IMPORT_CLOSING_DATABASE;
      message = getMessage(msgID);
      logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE,
               message, msgID);
      env.close();
    }
    long finishTime = System.currentTimeMillis();
@@ -923,7 +892,7 @@
    public ProgressTask() throws DatabaseException
    {
      previousTime = System.currentTimeMillis();
      prevEnvStats = env.getStats(new StatsConfig());
      prevEnvStats = rootContainer.getEnvironmentStats(new StatsConfig());
    }
    /**
@@ -957,7 +926,8 @@
        Runtime runtime = Runtime.getRuntime();
        long freeMemory = runtime.freeMemory() / bytesPerMegabyte;
        EnvironmentStats envStats = env.getStats(new StatsConfig());
        EnvironmentStats envStats =
            rootContainer.getEnvironmentStats(new StatsConfig());
        long nCacheMiss =
             envStats.getNCacheMiss() - prevEnvStats.getNCacheMiss();
opends/src/server/org/opends/server/backends/jeb/ImportThread.java
@@ -58,17 +58,17 @@
  private ImportContext importContext;
  /**
   * The destination entry container for entries read from the queue.
   * The destination entry entryContainer for entries read from the queue.
   */
  private EntryContainer entryContainer;
  /**
   * The entry database of the destination entry container.
   * The entry database of the destination entry entryContainer.
   */
  private ID2Entry id2entry;
  /**
   * The referral database of the destination entry container.
   * The referral database of the destination entry entryContainer.
   */
  private DN2URI dn2uri;
opends/src/server/org/opends/server/backends/jeb/Index.java
@@ -65,9 +65,9 @@
  /**
   * The database container holding this index database.
   * The database entryContainer holding this index database.
   */
  private Container container;
  private EntryContainer entryContainer;
  /**
   * The JE database configuration.
@@ -75,7 +75,7 @@
  private DatabaseConfig dbConfig;
  /**
   * The name of the database within the container.
   * The name of the database within the entryContainer.
   */
  private String name;
@@ -114,8 +114,8 @@
  /**
   * Create a new index object.
   * @param container The database container holding this index.
   * @param name The name of the index database within the container.
   * @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 indexEntryLimit The configured limit on the number of entry IDs
@@ -123,10 +123,10 @@
   * @param cursorEntryLimit The configured limit on the number of entry IDs
   * that may be retrieved by cursoring through an index.
   */
  public Index(Container container, String name, Indexer indexer,
  public Index(EntryContainer entryContainer, String name, Indexer indexer,
               int indexEntryLimit, int cursorEntryLimit)
  {
    this.container = container;
    this.entryContainer = entryContainer;
    this.name = name;
    this.indexer = indexer;
    this.comparator = indexer.getComparator();
@@ -148,7 +148,7 @@
  /**
   * Get a handle to the database. It returns a per-thread handle to avoid
   * any thread contention on the database handle. The container is
   * any thread contention on the database handle. The entryContainer is
   * responsible for closing all handles.
   *
   * @return A database handle.
@@ -160,7 +160,7 @@
    Database database = threadLocalDatabase.get();
    if (database == null)
    {
      database = container.openDatabase(dbConfig, name);
      database = entryContainer.openDatabase(dbConfig, name);
      threadLocalDatabase.set(database);
    }
    return database;
@@ -182,7 +182,7 @@
    DatabaseEntry entryIDData = entryID.getDatabaseEntry();
    DatabaseEntry data = new DatabaseEntry();
    status = Container.read(getDatabase(), txn, key, data, lockMode);
    status = EntryContainer.read(getDatabase(), txn, key, data, lockMode);
    if (status == OperationStatus.SUCCESS)
    {
@@ -202,12 +202,12 @@
        byte[] after = entryIDList.toDatabase();
        data.setData(after);
        Container.put(getDatabase(), txn, key, data);
        EntryContainer.put(getDatabase(), txn, key, data);
      }
    }
    else
    {
      Container.put(getDatabase(), txn, key, entryIDData);
      EntryContainer.put(getDatabase(), txn, key, entryIDData);
    }
  }
@@ -226,7 +226,7 @@
    LockMode lockMode = LockMode.RMW;
    DatabaseEntry data = new DatabaseEntry();
    status = Container.read(getDatabase(), txn, key, data, lockMode);
    status = EntryContainer.read(getDatabase(), txn, key, data, lockMode);
    if (status == OperationStatus.SUCCESS)
    {
@@ -244,12 +244,12 @@
          if (after == null)
          {
            // No more IDs, so remove the key
            Container.delete(getDatabase(), txn, key);
            EntryContainer.delete(getDatabase(), txn, key);
          }
          else
          {
            data.setData(after);
            Container.put(getDatabase(), txn, key, data);
            EntryContainer.put(getDatabase(), txn, key, data);
          }
        }
      }
@@ -280,7 +280,7 @@
    LockMode lockMode = LockMode.DEFAULT;
    DatabaseEntry data = new DatabaseEntry();
    status = Container.read(getDatabase(), txn, key, data, lockMode);
    status = EntryContainer.read(getDatabase(), txn, key, data, lockMode);
    if (status == OperationStatus.SUCCESS)
    {
      EntryIDSet entryIDList =
@@ -320,7 +320,7 @@
    {
      OperationStatus status;
      DatabaseEntry data = new DatabaseEntry();
      status = Container.read(getDatabase(), txn, key, data, lockMode);
      status = EntryContainer.read(getDatabase(), txn, key, data, lockMode);
      if (status != OperationStatus.SUCCESS)
      {
        return new EntryIDSet(key.getData(), null);
@@ -351,7 +351,7 @@
    if (after == null)
    {
      // No more IDs, so remove the key.
      Container.delete(getDatabase(), txn, key);
      EntryContainer.delete(getDatabase(), txn, key);
    }
    else
    {
@@ -360,7 +360,7 @@
        entryLimitExceededCount++;
      }
      data.setData(after);
      Container.put(getDatabase(), txn, key, data);
      EntryContainer.put(getDatabase(), txn, key, data);
    }
  }
opends/src/server/org/opends/server/backends/jeb/IndexFilter.java
@@ -48,7 +48,7 @@
  public static final int FILTER_CANDIDATE_THRESHOLD = 10;
  /**
   * The entry container holding the attribute indexes.
   * The entry entryContainer holding the attribute indexes.
   */
  private EntryContainer entryContainer;
@@ -67,7 +67,7 @@
  /**
   * Construct an index filter for a search operation.
   *
   * @param entryContainer The entry container.
   * @param entryContainer The entry entryContainer.
   * @param searchOp       The search operation to be evaluated.
   *
   * @param debugBuilder If not null, a diagnostic string will be written
opends/src/server/org/opends/server/backends/jeb/RootContainer.java
New file
@@ -0,0 +1,622 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.backends.jeb;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentStats;
import com.sleepycat.je.PreloadConfig;
import com.sleepycat.je.PreloadStats;
import com.sleepycat.je.PreloadStatus;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.config.ConfigParam;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.CheckpointConfig;
import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
import java.io.File;
import java.io.FilenameFilter;
import org.opends.server.monitors.DatabaseEnvironmentMonitor;
import org.opends.server.types.*;
import org.opends.server.loggers.Debug;
import static org.opends.server.loggers.Error.logError;
import static org.opends.server.loggers.Debug.debugException;
import static org.opends.server.loggers.Debug.debugEnter;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.JebMessages.
    MSGID_JEB_CACHE_SIZE_AFTER_PRELOAD;
import static org.opends.server.messages.JebMessages.
    MSGID_JEB_CLEAN_DATABASE_START;
import static org.opends.server.messages.JebMessages.
    MSGID_JEB_CLEAN_DATABASE_MARKED;
import static org.opends.server.messages.JebMessages.
    MSGID_JEB_CLEAN_DATABASE_FINISH;
import static org.opends.server.messages.JebMessages.
    MSGID_JEB_SET_PERMISSIONS_FAILED;
import org.opends.server.api.Backend;
/**
 * Wrapper class for the JE environment. Root container holds all the entry
 * containers for each base DN. It also maintains all the openings and closings
 * of the entry containers.
 */
public class RootContainer
{
    /**
   * The fully-qualified name of this class for debugging purposes.
   */
  private static final String CLASS_NAME =
       "org.opends.server.backends.jeb.RootContainer";
  /**
   * The JE database environment.
   */
  private Environment env;
  /**
   * The backend configuration.
   */
  private Config config;
  /**
   * The backend to which this entry root container belongs.
   */
  private Backend backend;
  /**
   * The database environment monitor for this JE environment.
   */
  private DatabaseEnvironmentMonitor monitor;
  /**
   * A configurable component to handle changes to the configuration of
   * the database environment.
   */
  private ConfigurableEnvironment configurableEnv;
  /**
   * The base DNs contained in this entryContainer.
   */
  private ConcurrentHashMap<DN, EntryContainer> entryContainers;
  /**
   * Creates a new RootContainer object. Each root container represents a JE
   * environment.
   *
   * @param config The configuration of the JE backend.
   * @param backend A reference to the JE back end that is creating this
   *                root container.
   */
  public RootContainer(Config config, Backend backend)
  {
    this.env = null;
    this.configurableEnv = null;
    this.monitor = null;
    this.entryContainers = new ConcurrentHashMap<DN, EntryContainer>();
    this.backend = backend;
    this.config = config;
  }
  /**
   * Helper method to apply database directory permissions and create a new
   * JE environment.
   *
   * @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.
   */
  private void open(File backendDirectory,
                    FilePermission backendPermission,
                    EnvironmentConfig envConfig) throws DatabaseException
  {
    // Get the backend database backendDirectory permissions and apply
    try
    {
      if(!FilePermission.setPermissions(backendDirectory, backendPermission))
      {
        throw new Exception();
      }
    }
    catch(Exception e)
    {
      // Log an warning that the permissions were not set.
      int msgID = MSGID_JEB_SET_PERMISSIONS_FAILED;
      String message = getMessage(msgID, backendDirectory.getPath());
      logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_WARNING,
               message, msgID);
    }
    // Open the database environment
    env = new Environment(backendDirectory,
                          envConfig);
    Debug.debugMessage(DebugLogCategory.BACKEND, DebugLogSeverity.INFO,
                         CLASS_NAME, "initializeBackend",
                         env.getConfig().toString());
  }
  /**
   * 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);
  }
  /**
   * Opens the entry container for a base DN. If the entry container does not
   * exist for the base DN, it will be created. The entry container will be
   * opened with the same mode as the root container. Any entry containers
   * opened in a read only root container will also be read only. Any entry
   * containers opened in a non transactional root container will also be non
   * transactional.
   *
   * @param baseDN The base DN of the entry container to open.
   * @return The opened entry container.
   * @throws DatabaseException If an error occurs while opening the entry
   *                           container.
   */
  public EntryContainer openEntryContainer(DN baseDN) throws DatabaseException
  {
    EntryContainer ec = new EntryContainer(baseDN, backend, config, env);
    if(env.getConfig().getReadOnly())
    {
      ec.openReadOnly();
    }
    else if(!env.getConfig().getTransactional())
    {
      ec.openNonTransactional(true);
    }
    else
    {
      ec.open();
    }
    this.entryContainers.put(baseDN, ec);
    return ec;
  }
  /**
   * Opens the entry containers for multiple base DNs.
   *
   * @param baseDNs The base DNs of the entry containers to open.
   * @throws DatabaseException If an error occurs while opening the entry
   *                           container.
   */
  public void openEntryContainers(DN[] baseDNs) throws DatabaseException
  {
    for(DN baseDN : baseDNs)
    {
      openEntryContainer(baseDN);
    }
  }
  /**
   * Close the entry container for a base DN.
   *
   * @param baseDN The base DN of the entry container to close.
   * @throws DatabaseException If an error occurs while closing the entry
   *                           container.
   */
  public void closeEntryContainer(DN baseDN) throws DatabaseException
  {
    getEntryContainer(baseDN).close();
    entryContainers.remove(baseDN);
  }
  /**
   * Close and remove a entry container for a base DN from disk.
   *
   * @param baseDN The base DN of the entry container to remove.
   * @throws DatabaseException If an error occurs while removing the entry
   *                           container.
   */
  public void removeEntryContainer(DN baseDN) throws DatabaseException
  {
    getEntryContainer(baseDN).close();
    getEntryContainer(baseDN).removeContainer();
    entryContainers.remove(baseDN);
  }
  /**
   * Get the ConfigurableEnvironment object for JE environment used by this
   * root container.
   *
   * @return The ConfigurableEnvironment object.
   */
  public ConfigurableEnvironment getConfigurableEnvironment()
  {
    if(configurableEnv == null)
    {
      DN envConfigDN = config.getEnvConfigDN();
      if (envConfigDN != null)
      {
        configurableEnv = new ConfigurableEnvironment(envConfigDN, env);
      }
    }
    return configurableEnv;
  }
  /**
   * Get the DatabaseEnvironmentMonitor object for JE environment used by this
   * root container.
   *
   * @return The DatabaseEnvironmentMonito object.
   */
  public DatabaseEnvironmentMonitor getMonitorProvider()
  {
    if(monitor == null)
    {
      String monitorName = backend.getBackendID() + " Database Environment";
      monitor = new DatabaseEnvironmentMonitor(monitorName, env);
    }
    return monitor;
  }
  /**
   * Preload the database cache. There is no preload if the configured preload
   * time limit is zero.
   */
  public void preload()
  {
    assert debugEnter(CLASS_NAME, "preload");
    long timeLimit = config.getPreloadTimeLimit();
    if (timeLimit > 0)
    {
      // Get a list of all the databases used by the backend.
      ArrayList<Database> dbList = new ArrayList<Database>();
      for (EntryContainer ec : entryContainers.values())
      {
        ec.listDatabases(dbList);
      }
      // Sort the list in order of priority.
      Collections.sort(dbList, new DbPreloadComparator());
      // Preload each database until we reach the time limit or the cache
      // is filled.
      try
      {
        long timeEnd = System.currentTimeMillis() + timeLimit;
        // Configure preload of Leaf Nodes (LNs) containing the data values.
        PreloadConfig preloadConfig = new PreloadConfig();
        preloadConfig.setLoadLNs(true);
        for (Database db : dbList)
        {
          // Calculate the remaining time.
          long timeRemaining = timeEnd - System.currentTimeMillis();
          if (timeRemaining <= 0)
          {
            break;
          }
          preloadConfig.setMaxMillisecs(timeRemaining);
          PreloadStats preloadStats = db.preload(preloadConfig);
/*
          System.out.println("file=" + db.getDatabaseName() +
                             " LNs=" + preloadStats.getNLNsLoaded());
*/
          // Stop if the cache is full or the time limit has been exceeded.
          if (preloadStats.getStatus() != PreloadStatus.SUCCESS)
          {
            break;
          }
        }
        // Log an informational message about the size of the cache.
        EnvironmentStats stats = env.getStats(new StatsConfig());
        long total = stats.getCacheTotalBytes();
        int msgID = MSGID_JEB_CACHE_SIZE_AFTER_PRELOAD;
        String message = getMessage(msgID, total / (1024 * 1024));
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE, message,
                 msgID);
      }
      catch (DatabaseException e)
      {
        assert debugException(CLASS_NAME, "preload", e);
      }
    }
  }
  /**
   * Synchronously invokes the cleaner on the database environment then forces a
   * checkpoint to delete the log files that are no longer in use.
   *
   * @throws DatabaseException If an error occurs while cleaning the database
   * environment.
   */
  private void cleanDatabase()
       throws DatabaseException
  {
    assert debugEnter(CLASS_NAME, "cleanDatabase");
    int msgID;
    String message;
    FilenameFilter filenameFilter = new FilenameFilter()
    {
      public boolean accept(File d, String name)
      {
        return name.endsWith(".jdb");
      }
    };
    File backendDirectory = env.getHome();
    int beforeLogfileCount = backendDirectory.list(filenameFilter).length;
    msgID = MSGID_JEB_CLEAN_DATABASE_START;
    message = getMessage(msgID, beforeLogfileCount, backendDirectory.getPath());
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE, message,
             msgID);
    int currentCleaned = 0;
    int totalCleaned = 0;
    while ((currentCleaned = env.cleanLog()) > 0)
    {
      totalCleaned += currentCleaned;
    }
    msgID = MSGID_JEB_CLEAN_DATABASE_MARKED;
    message = getMessage(msgID, totalCleaned);
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE, message,
             msgID);
    if (totalCleaned > 0)
    {
      CheckpointConfig force = new CheckpointConfig();
      force.setForce(true);
      env.checkpoint(force);
    }
    int afterLogfileCount = backendDirectory.list(filenameFilter).length;
    msgID = MSGID_JEB_CLEAN_DATABASE_FINISH;
    message = getMessage(msgID, afterLogfileCount);
    logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.NOTICE, message,
             msgID);
  }
  /**
   * Close the root entryContainer.
   *
   * @throws DatabaseException If an error occurs while attempting to close
   * the entryContainer.
   */
  public void close() throws DatabaseException
  {
    for(DN baseDN : entryContainers.keySet())
    {
      entryContainers.get(baseDN).close();
      entryContainers.remove(baseDN);
    }
    env.close();
  }
  /**
   * Return all the entry containers in this root container.
   *
   * @return The entry containers in this root container.
   */
  public Collection<EntryContainer> getEntryContainers()
  {
    return entryContainers.values();
  }
  /**
   * Returns all the baseDNs this root container stores.
   *
   * @return The set of DNs this root container stores.
   */
  public Set<DN> getBaseDNs()
  {
    return entryContainers.keySet();
  }
  /**
   * Return the entry container for a specific base DN.
   *
   * @param baseDN The base DN of the entry container to retrive.
   * @return The entry container for the base DN.
   */
  public EntryContainer getEntryContainer(DN baseDN)
  {
    EntryContainer ec = null;
    DN nodeDN = baseDN;
    while (ec == null && nodeDN != null)
    {
      ec = entryContainers.get(nodeDN);
      if (ec == null)
      {
        nodeDN = nodeDN.getParent();
      }
    }
    return ec;
  }
  /**
   * Apply new configuration to the JE environment.
   *
   * @param newConfig The new configuration to apply.
   * @throws DatabaseException If an error occurs while applying the new
   *                           configuration.
   */
  public void applyNewConfig(Config newConfig) throws DatabaseException
  {
    // Check for changes to the database directory permissions
    FilePermission oldPermission = config.getBackendPermission();
    FilePermission newPermission = newConfig.getBackendPermission();
    if(!FilePermission.toUNIXMode(oldPermission).equals(
        FilePermission.toUNIXMode(newPermission)))
    {
      try
      {
        if(!FilePermission.setPermissions(newConfig.getBackendDirectory(),
                                          newPermission))
        {
          throw new Exception();
        }
      }
      catch(Exception e)
      {
        // Log an warning that the permissions were not set.
        int msgID = MSGID_JEB_SET_PERMISSIONS_FAILED;
        String message = getMessage(msgID,
                                    config.getBackendDirectory().getPath());
        logError(ErrorLogCategory.BACKEND, ErrorLogSeverity.SEVERE_WARNING,
                 message, msgID);
      }
    }
    // Check if any JE non-mutable properties were changed.
    EnvironmentConfig oldEnvConfig = this.config.getEnvironmentConfig();
    EnvironmentConfig newEnvConfig = newConfig.getEnvironmentConfig();
    Map paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
    for (Object o : paramsMap.values())
    {
      ConfigParam param = (ConfigParam)o;
      if (!param.isMutable())
      {
        String oldValue = oldEnvConfig.getConfigParam(param.getName());
        String newValue = newEnvConfig.getConfigParam(param.getName());
        if (!oldValue.equalsIgnoreCase(newValue))
        {
          System.out.println("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(newConfig.getEnvironmentConfig());
    config = newConfig;
    Debug.debugMessage(DebugLogCategory.BACKEND, DebugLogSeverity.INFO,
                       CLASS_NAME, "applyNewConfiguration",
                       env.getConfig().toString());
  }
  /**
   * Get the environment 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 EnvironmentStats getEnvironmentStats(StatsConfig statsConfig)
      throws DatabaseException
  {
    return env.getStats(statsConfig);
  }
  /**
   * Get the environment config of the JE environment used in this root
   * 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();
  }
}
opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
@@ -32,26 +32,20 @@
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentStats;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.Transaction;
import org.opends.server.api.Backend;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.Debug;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.DebugLogCategory;
import org.opends.server.types.DebugLogSeverity;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
@@ -65,7 +59,6 @@
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.JebMessages.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -94,19 +87,14 @@
  private VerifyConfig verifyConfig;
  /**
   * The JE backend to be verified.
   */
  private Backend backend;
  /**
   * The configuration of the JE backend.
   */
  private Config config;
  /**
   * A read-only JE database environment handle for the purpose of verification.
   * The root container used for the verify job.
   */
  private Environment env;
  RootContainer rootContainer;
  /**
   * The number of milliseconds between job progress reports.
@@ -193,44 +181,29 @@
  /**
   * Construct a VerifyJob.
   *
   * @param backend The backend performing the verify process.
   * @param config The backend configuration.
   * @param verifyConfig The verify configuration.
   */
  public VerifyJob(Backend backend, Config config, VerifyConfig verifyConfig)
  public VerifyJob(Config config, VerifyConfig verifyConfig)
  {
    this.verifyConfig = verifyConfig;
    this.backend = backend;
    this.config = config;
  }
  /**
   * Verify the backend.
   *
   * @param rootContainer The root container that holds the entries to verify.
   * @throws DatabaseException If an error occurs in the JE database.
   * @throws JebException If an error occurs in the JE backend.
   */
  public void verifyBackend() throws DatabaseException, JebException
  public void verifyBackend(RootContainer rootContainer) throws
      DatabaseException, JebException
  {
    File backendDirectory = config.getBackendDirectory();
    // Open the environment read-only.
    EnvironmentConfig envConfig = config.getEnvironmentConfig();
    envConfig.setReadOnly(true);
    envConfig.setAllowCreate(false);
    envConfig.setTransactional(false);
    env = new Environment(backendDirectory, envConfig);
    Debug.debugMessage(DebugLogCategory.BACKEND, DebugLogSeverity.INFO,
                       CLASS_NAME, "verifyBackend",
                       env.getConfig().toString());
    // Open a container read-only.
    String containerName =
         BackendImpl.getContainerName(verifyConfig.getBaseDN());
    Container container = new Container(env, containerName);
    this.rootContainer = rootContainer;
    EntryContainer entryContainer =
         new EntryContainer(backend, config, container);
    entryContainer.openReadOnly();
        rootContainer.getEntryContainer(verifyConfig.getBaseDN());
    ArrayList<String> completeList = verifyConfig.getCompleteList();
    ArrayList<String> cleanList = verifyConfig.getCleanList();
@@ -304,7 +277,7 @@
    // We will be updating these files independently of the indexes
    // so we need direct access to them rather than going through
    // the entry container methods.
    // the entry entryContainer methods.
    id2entry = entryContainer.getID2Entry();
    dn2id = entryContainer.getDN2ID();
    id2c = entryContainer.getID2Children();
@@ -1567,7 +1540,8 @@
    public ProgressTask() throws DatabaseException
    {
      previousTime = System.currentTimeMillis();
      prevEnvStats = env.getStats(new StatsConfig());
      prevEnvStats =
          rootContainer.getEnvironmentStats(new StatsConfig());
    }
    /**
@@ -1597,7 +1571,8 @@
        Runtime runtime = Runtime.getRuntime();
        long freeMemory = runtime.freeMemory() / bytesPerMegabyte;
        EnvironmentStats envStats = env.getStats(new StatsConfig());
        EnvironmentStats envStats =
            rootContainer.getEnvironmentStats(new StatsConfig());
        long nCacheMiss =
             envStats.getNCacheMiss() - prevEnvStats.getNCacheMiss();
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestEntryContainer.java
@@ -35,8 +35,11 @@
import java.util.ArrayList;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Entry;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.FilePermission;
import org.opends.server.types.DN;
import org.opends.server.util.LDIFReader;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeClass;
@@ -181,14 +184,14 @@
  @Test()
  public void test1() throws Exception {
    EnvManager.createHomeDir(homeDirName);
    EnvironmentConfig envConfig = new EnvironmentConfig();
    envConfig.setTransactional(true);
    envConfig.setAllowCreate(true);
    Environment env = new Environment(new File(homeDirName), envConfig);
    EntryContainer entryContainer = new EntryContainer(null, new Config(),
        new Container(env, null));
    RootContainer rootContainer = new RootContainer(new Config(), null);
    rootContainer.open(new File(homeDirName),
                       new FilePermission(true, true, true),
                       false, true, true, false, true, true);
    entryContainer.open();
    EntryContainer entryContainer =
        rootContainer.openEntryContainer(DirectoryServer.getSchemaDN());
    EntryID actualHighestID = entryContainer.getHighestEntryID();
    assertTrue(actualHighestID.equals(new EntryID(0)));
@@ -201,9 +204,7 @@
    actualHighestID = entryContainer.getHighestEntryID();
    assertTrue(actualHighestID.equals(new EntryID(calculatedHighestID)));
    entryContainer.close();
    env.close();
    rootContainer.close();
    EnvManager.removeFiles(homeDirName);
  }
}