From 5031429bf032af5e3d8797210cc47b402ef831d6 Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Tue, 16 Dec 2014 23:48:24 +0000
Subject: [PATCH] OPENDJ-1602 (CR-5566) New pluggable storage based backend

---
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java                     |    3 
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/IndexFilter.java                |    1 
 opendj3-server-dev/src/server/org/opends/server/backends/persistit/PersistItStorage.java           |  431 ++++++++++++
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/NullIndex.java                  |    2 
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SuffixContainer.java            |    8 
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VLVIndex.java                   |  122 +--
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseEnvironmentMonitor.java |  394 +++++++++++
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/Index.java                      |    2 
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VerifyJob.java                  |   59 -
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SortValuesSet.java              |   10 
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseContainer.java          |    2 
 /dev/null                                                                                          |   90 --
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/AttributeIndex.java             |   16 
 opendj3-server-dev/src/server/org/opends/server/backends/jeb/EntryContainer.java                   |    1 
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/RootContainer.java              |  291 ++-----
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EnvManager.java                 |   39 
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/ExportJob.java                  |    3 
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java             |  457 +++++-------
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/BackendImpl.java                |  129 +--
 opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryIDSetSorter.java           |    3 
 20 files changed, 1,263 insertions(+), 800 deletions(-)

diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/jeb/EntryContainer.java b/opendj3-server-dev/src/server/org/opends/server/backends/jeb/EntryContainer.java
index 1e1c2d5..a667fa4 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/jeb/EntryContainer.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -2687,7 +2687,6 @@
    * @return The number of entries stored in this entry container.
    * @throws DatabaseException If an error occurs in the JE database.
    */
-  @Override
   public long getEntryCount() throws DatabaseException
   {
     EntryID entryID = dn2id.get(null, baseDN, LockMode.DEFAULT);
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/persistit/PersistItStorage.java b/opendj3-server-dev/src/server/org/opends/server/backends/persistit/PersistItStorage.java
new file mode 100644
index 0000000..1abbddc
--- /dev/null
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/persistit/PersistItStorage.java
@@ -0,0 +1,431 @@
+package org.opends.server.backends.persistit;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.forgerock.opendj.ldap.ByteSequence;
+import org.forgerock.opendj.ldap.ByteString;
+import org.opends.server.admin.std.server.LocalDBBackendCfg;
+import org.opends.server.backends.pluggable.BackendImpl.Cursor;
+import org.opends.server.backends.pluggable.BackendImpl.Importer;
+import org.opends.server.backends.pluggable.BackendImpl.ReadOperation;
+import org.opends.server.backends.pluggable.BackendImpl.Storage;
+import org.opends.server.backends.pluggable.BackendImpl.StorageRuntimeException;
+import org.opends.server.backends.pluggable.BackendImpl.TreeName;
+import org.opends.server.backends.pluggable.BackendImpl.WriteOperation;
+import org.opends.server.backends.pluggable.BackendImpl.WriteableStorage;
+import org.opends.server.types.DN;
+
+import com.persistit.Exchange;
+import com.persistit.Key;
+import com.persistit.Persistit;
+import com.persistit.Transaction;
+import com.persistit.Tree;
+import com.persistit.TreeBuilder;
+import com.persistit.Value;
+import com.persistit.Volume;
+import com.persistit.exception.PersistitException;
+import com.persistit.exception.RollbackException;
+
+@SuppressWarnings("javadoc")
+public final class PersistItStorage implements Storage {
+    private final class ImporterImpl implements Importer {
+        private final Map<TreeName, Tree> trees = new HashMap<TreeName, Tree>();
+        private final TreeBuilder importer = new TreeBuilder(db);
+        private final Key importKey = new Key(db);
+        private final Value importValue = new Value(db);
+
+        @Override
+        public void createTree(TreeName treeName) {
+            try {
+                // FIXME: how do we set the comparator?
+                final Tree tree = getVolume(treeName).getTree(treeName.toString(), true);
+                trees.put(treeName, tree);
+            } catch (PersistitException e) {
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public void put(TreeName treeName, ByteSequence key, ByteSequence value) {
+            try {
+                final Tree tree = trees.get(treeName);
+                byte[] keyBytes = key.toByteArray();
+                importKey.clear().appendByteArray(keyBytes, 0, keyBytes.length);
+                importValue.clear().putByteArray(value.toByteArray());
+                importer.store(tree, importKey, importValue);
+            } catch (Exception e) {
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public void close() {
+            try {
+                importer.merge();
+            } catch (Exception e) {
+                throw new StorageRuntimeException(e);
+            } finally {
+                PersistItStorage.this.close();
+            }
+        }
+    }
+
+    private final class StorageImpl implements WriteableStorage {
+        private final Map<TreeName, Exchange> exchanges = new HashMap<TreeName, Exchange>();
+
+        private void release() {
+            for (Exchange ex : exchanges.values()) {
+                db.releaseExchange(ex);
+            }
+        }
+
+        private Exchange getExchange(TreeName treeName) throws PersistitException {
+            Exchange exchange = exchanges.get(treeName);
+            if (exchange == null) {
+                exchange = getExchange0(treeName, false);
+                exchanges.put(treeName, exchange);
+            }
+            return exchange;
+        }
+
+        @Override
+        public ByteString get(TreeName treeName, ByteSequence key) {
+            try {
+                final Exchange ex = getExchange(treeName);
+                ex.getKey().clear().append(key.toByteArray());
+                ex.fetch();
+                final Value value = ex.getValue();
+                if (value.isDefined()) {
+                    return ByteString.wrap(value.getByteArray());
+                }
+                return null;
+            } catch (PersistitException e) {
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public ByteString getRMW(TreeName treeName, ByteSequence key) {
+            return get(treeName, key);
+        }
+
+        @Override
+        public void put(TreeName treeName, ByteSequence key, ByteSequence value) {
+            try {
+                final Exchange ex = getExchange(treeName);
+                ex.getKey().clear().append(key.toByteArray());
+                ex.getValue().clear().putByteArray(value.toByteArray());
+                ex.store();
+            } catch (Exception e) {
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public boolean putIfAbsent(TreeName treeName, ByteSequence key, ByteSequence value) {
+            try {
+                final Exchange ex = getExchange(treeName);
+                ex.getKey().clear().append(key.toByteArray());
+                ex.fetch();
+                // FIXME poor man's CAS: this will not work under high volume,
+                // but PersistIt does not provide APIs for this use case.
+                if (ex.isValueDefined()) {
+                    return false;
+                }
+                ex.getValue().clear().putByteArray(value.toByteArray());
+                ex.store();
+                return true;
+            } catch (Exception e) {
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public boolean remove(TreeName treeName, ByteSequence key) {
+            try {
+                final Exchange ex = getExchange(treeName);
+                ex.getKey().clear().append(key.toByteArray());
+                return ex.remove();
+            } catch (PersistitException e) {
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public Cursor openCursor(TreeName treeName) {
+            try {
+                return new CursorImpl(getExchange(treeName));
+            } catch (PersistitException e) {
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public void openTree(TreeName treeName) {
+            Exchange ex = null;
+            try {
+                ex = getExchange0(treeName, true);
+            } catch (PersistitException e) {
+                throw new StorageRuntimeException(e);
+            } finally {
+                db.releaseExchange(ex);
+            }
+        }
+    }
+
+    private final class CursorImpl implements Cursor {
+
+        private final Exchange ex;
+        private boolean useCurrentKeyForNext = false;
+
+        public CursorImpl(Exchange exchange) {
+            this.ex = exchange;
+        }
+
+        @Override
+        public boolean positionToKey(ByteSequence key) {
+            ex.getKey().clear().append(key.toByteArray());
+            try {
+                ex.fetch();
+                useCurrentKeyForNext = ex.getValue().isDefined();
+                return useCurrentKeyForNext;
+            } catch (PersistitException e) {
+                useCurrentKeyForNext = false;
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public boolean positionToKeyOrNext(ByteSequence key) {
+            ex.getKey().clear().append(key.toByteArray());
+            try {
+                ex.fetch();
+                if (ex.getValue().isDefined()) {
+                    useCurrentKeyForNext = true;
+                } else {
+                    // provided key does not exist, look for next key
+                    useCurrentKeyForNext = ex.next();
+                }
+                return useCurrentKeyForNext;
+            } catch (PersistitException e) {
+                useCurrentKeyForNext = false;
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public boolean positionToLastKey() {
+            try {
+                ex.getKey().to(Key.AFTER);
+                useCurrentKeyForNext = ex.previous() && ex.getValue().isDefined();
+                return useCurrentKeyForNext;
+            } catch (PersistitException e) {
+                useCurrentKeyForNext = false;
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public boolean next() {
+            if (useCurrentKeyForNext) {
+                useCurrentKeyForNext = false;
+                return true;
+            }
+            try {
+                return ex.next();
+            } catch (PersistitException e) {
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public boolean previous() {
+            try {
+                return ex.previous();
+            } catch (PersistitException e) {
+                throw new StorageRuntimeException(e);
+            }
+        }
+
+        @Override
+        public ByteString getKey() {
+            return ByteString.wrap(ex.getKey().decodeByteArray());
+        }
+
+        @Override
+        public ByteString getValue() {
+            return ByteString.wrap(ex.getValue().getByteArray());
+        }
+
+        @Override
+        public void close() {
+            // Exchange is released by StorageImpl.release()
+            // once the Read/Write Operation is closed
+        }
+    }
+
+    private final File backendDirectory;
+    private final LocalDBBackendCfg config;
+    private Persistit db;
+    private final ConcurrentMap<TreeName, Volume> volumes = new ConcurrentHashMap<TreeName, Volume>();
+    private Properties properties;
+
+    public PersistItStorage(File backendDirectory, LocalDBBackendCfg config) {
+        this.backendDirectory = backendDirectory;
+        this.config = config;
+    }
+
+    private Volume getVolume(TreeName treeName) {
+        return volumes.get(treeName.getSuffix());
+    }
+
+    @Override
+    public void initialize(Map<String, String> options) {
+        properties = new Properties();
+        properties.setProperty("datapath", backendDirectory.toString());
+        properties.setProperty("logpath", backendDirectory.toString());
+        properties.setProperty("logfile", "${logpath}/dj_${timestamp}.log");
+        properties.setProperty("buffer.count.16384", "64K");
+        properties.setProperty("journalpath", "${datapath}/dj_journal");
+        int i = 1;
+        for (DN baseDN : config.getBaseDN()) {
+            // TODO use VolumeSpecification  Configuration.setVolumeList()?
+            properties.setProperty("volume." + i++,
+                "${datapath}/" + toSuffixName(baseDN.toString())
+                    + ",create,pageSize:16K"
+                    + ",initialSize:50M"
+                    + ",extensionSize:1M"
+                    + ",maximumSize:10G");
+        }
+
+        if (options != null) {
+            for (Entry<String, String> entry : options.entrySet()) {
+                properties.setProperty(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+
+    /**
+     * Replace persistit reserved comma character with an underscore character.
+     */
+    public String toSuffixName(String prefix) {
+        return prefix.replaceAll("[,=]", "_");
+    }
+
+    @Override
+    public void open() {
+        try {
+            db = new Persistit(properties);
+            db.initialize();
+            for (DN baseDN : config.getBaseDN()) {
+                final String volumeName = toSuffixName(baseDN.toString());
+                final TreeName suffixName = TreeName.of(volumeName);
+                volumes.put(suffixName, db.loadVolume(volumeName));
+            }
+        } catch (PersistitException e) {
+            throw new StorageRuntimeException(e);
+        }
+    }
+
+    @Override
+    public void close() {
+        if (db != null) {
+            try {
+                db.close();
+                db = null;
+            } catch (PersistitException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+    static void clearAndCreateDbDir(final File dbDir) {
+        if (dbDir.exists()) {
+            for (final File child : dbDir.listFiles()) {
+                child.delete();
+            }
+        } else {
+            dbDir.mkdirs();
+        }
+    }
+
+    @Override
+    public Importer startImport() {
+        clearAndCreateDbDir(backendDirectory);
+        open();
+        return new ImporterImpl();
+    }
+
+    @Override
+    public <T> T read(ReadOperation<T> operation) throws Exception {
+        final Transaction txn = db.getTransaction();
+        for (;;) {
+            txn.begin();
+            try {
+                final StorageImpl storageImpl = new StorageImpl();
+                try {
+                    final T result = operation.run(storageImpl);
+                    txn.commit();
+                    return result;
+                } catch (StorageRuntimeException e) {
+                    throw (Exception) e.getCause();
+                } finally {
+                    storageImpl.release();
+                }
+            } catch (RollbackException e) {
+                // retry
+            } catch (Exception e) {
+                txn.rollback();
+                throw e;
+            } finally {
+                txn.end();
+            }
+        }
+    }
+
+    @Override
+    public void write(WriteOperation operation) throws Exception {
+        final Transaction txn = db.getTransaction();
+        for (;;) {
+            txn.begin();
+            try {
+                final StorageImpl storageImpl = new StorageImpl();
+                try {
+                    operation.run(storageImpl);
+                    txn.commit();
+                    return;
+                } catch (StorageRuntimeException e) {
+                    throw (Exception) e.getCause();
+                } finally {
+                    storageImpl.release();
+                }
+            } catch (RollbackException e) {
+                // retry
+            } catch (Exception e) {
+                txn.rollback();
+                throw e;
+            } finally {
+                txn.end();
+            }
+        }
+    }
+
+    @Override
+    public Cursor openCursor(TreeName treeName) {
+        try {
+            return new CursorImpl(getExchange0(treeName, false));//FIXME JNR we must release the exchange
+        } catch (PersistitException e) {
+            throw new StorageRuntimeException(e);
+        }
+    }
+
+    private Exchange getExchange0(TreeName treeName, boolean create) throws PersistitException {
+        return db.getExchange(getVolume(treeName), treeName.toString(), create);
+    }
+}
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/AttributeIndex.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/AttributeIndex.java
index 2183b19..014d9f7 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/AttributeIndex.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/AttributeIndex.java
@@ -49,7 +49,6 @@
 import org.opends.server.backends.pluggable.BackendImpl.WriteOperation;
 import org.opends.server.backends.pluggable.BackendImpl.WriteableStorage;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.monitors.DatabaseEnvironmentMonitor;
 import org.opends.server.types.*;
 import org.opends.server.util.StaticUtils;
 
@@ -716,11 +715,12 @@
         }
       }
     }
-    removeIndexesForExtensibleMatchingRules(validRules, validIndexIds);
+    removeIndexesForExtensibleMatchingRules(txn, validRules, validIndexIds);
   }
 
   /** Remove indexes which do not correspond to valid rules. */
-  private void removeIndexesForExtensibleMatchingRules(Set<MatchingRule> validRules, Set<String> validIndexIds)
+  private void removeIndexesForExtensibleMatchingRules(WriteableStorage txn, Set<MatchingRule> validRules,
+      Set<String> validIndexIds)
   {
     final Set<MatchingRule> rulesToDelete = getCurrentExtensibleMatchingRules();
     rulesToDelete.removeAll(validRules);
@@ -746,7 +746,7 @@
             Index index = nameToIndexes.get(indexId);
             if (index != null)
             {
-              entryContainer.deleteDatabase(index);
+              entryContainer.deleteDatabase(txn, index);
               nameToIndexes.remove(index);
             }
           }
@@ -779,7 +779,7 @@
     Index index = nameToIndexes.get(indexId);
     if (!cfg.getIndexType().contains(indexType))
     {
-      removeIndex(index, indexType);
+      removeIndex(txn, index, indexType);
       return;
     }
 
@@ -811,7 +811,7 @@
     Index index = nameToIndexes.get(indexID);
     if (!cfg.getIndexType().contains(indexType))
     {
-      removeIndex(index, indexType);
+      removeIndex(txn, index, indexType);
       return;
     }
 
@@ -832,7 +832,7 @@
     }
   }
 
-  private void removeIndex(Index index, IndexType indexType)
+  private void removeIndex(WriteableStorage txn, Index index, IndexType indexType)
   {
     if (index != null)
     {
@@ -840,7 +840,7 @@
       try
       {
         nameToIndexes.remove(indexType.toString());
-        entryContainer.deleteDatabase(index);
+        entryContainer.deleteDatabase(txn, index);
       }
       finally
       {
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/BackendImpl.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/BackendImpl.java
index 8411478..2334b57 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/BackendImpl.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/BackendImpl.java
@@ -54,16 +54,12 @@
 import org.opends.server.api.Backend;
 import org.opends.server.api.DiskSpaceMonitorHandler;
 import org.opends.server.api.MonitorProvider;
+import org.opends.server.backends.pluggable.BackendImpl.WriteableStorage;
 import org.opends.server.core.*;
 import org.opends.server.extensions.DiskSpaceMonitor;
 import org.opends.server.types.*;
 import org.opends.server.util.RuntimeInformation;
 
-import com.sleepycat.je.Durability;
-import com.sleepycat.je.EnvironmentConfig;
-
-import static com.sleepycat.je.EnvironmentConfig.*;
-
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.messages.JebMessages.*;
 import static org.opends.server.backends.jeb.ConfigurableEnvironment.*;
@@ -408,7 +404,7 @@
 
     if (mustOpenRootContainer())
     {
-      rootContainer = initializeRootContainer(parseConfigEntry(cfg));
+      rootContainer = initializeRootContainer();
     }
 
     // Preload the database cache.
@@ -417,7 +413,7 @@
     try
     {
       // Log an informational message about the number of entries.
-      logger.info(NOTE_JEB_BACKEND_STARTED, cfg.getBackendId(), rootContainer.getEntryCount());
+      logger.info(NOTE_JEB_BACKEND_STARTED, cfg.getBackendId(), getEntryCount());
     }
     catch (StorageRuntimeException e)
     {
@@ -640,7 +636,6 @@
         logger.traceException(e);
       }
     }
-
     return -1;
   }
 
@@ -988,7 +983,7 @@
 
       throw new NotImplementedException();
 //      Importer importer = new Importer(importConfig, cfg, envConfig);
-//      rootContainer = initializeRootContainer(envConfig);
+//      rootContainer = initializeRootContainer();
 //      return importer.processImport(rootContainer);
     }
     catch (ExecutionException execEx)
@@ -1005,10 +1000,10 @@
       logger.traceException(intEx);
       throw new DirectoryException(errorRC, ERR_INTERRUPTED_ERROR.get(intEx.getMessage()));
     }
-    catch (JebException je)
+    catch (StorageRuntimeException e)
     {
-      logger.traceException(je);
-      throw new DirectoryException(errorRC, je.getMessageObject());
+      logger.traceException(e);
+      throw new DirectoryException(errorRC, LocalizableMessage.raw(e.getMessage()));
     }
     catch (InitializationException ie)
     {
@@ -1093,12 +1088,6 @@
       logger.traceException(e);
       throw createDirectoryException(e);
     }
-    catch (JebException e)
-    {
-      logger.traceException(e);
-      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
-                                   e.getMessageObject());
-    }
     finally
     {
       closeTemporaryRootContainer(openRootContainer);
@@ -1141,7 +1130,7 @@
       if (openRootContainer)
       {
         envConfig = getEnvConfigForImport();
-        rootContainer = initializeRootContainer(envConfig);
+        rootContainer = initializeRootContainer();
       }
       else
       {
@@ -1167,10 +1156,10 @@
       logger.traceException(ce);
       throw new DirectoryException(errorRC, ce.getMessageObject());
     }
-    catch (JebException e)
+    catch (StorageRuntimeException e)
     {
       logger.traceException(e);
-      throw new DirectoryException(errorRC, e.getMessageObject());
+      throw new DirectoryException(errorRC, LocalizableMessage.raw(e.getMessage()));
     }
     catch (InitializationException e)
     {
@@ -1270,50 +1259,52 @@
 
   /** {@inheritDoc} */
   @Override
-  public ConfigChangeResult applyConfigurationChange(LocalDBBackendCfg newCfg)
+  public ConfigChangeResult applyConfigurationChange(final LocalDBBackendCfg newCfg)
   {
-    ResultCode resultCode = ResultCode.SUCCESS;
-    ArrayList<LocalizableMessage> messages = new ArrayList<LocalizableMessage>();
-
+    final ConfigChangeResult ccr = new ConfigChangeResult();
     try
     {
       if(rootContainer != null)
       {
-        SortedSet<DN> newBaseDNs = newCfg.getBaseDN();
-        DN[] newBaseDNsArray = newBaseDNs.toArray(new DN[newBaseDNs.size()]);
-
-        // Check for changes to the base DNs.
-        removeDeletedBaseDNs(newBaseDNs);
-        ConfigChangeResult failure = createNewBaseDNs(newBaseDNsArray, messages);
-        if (failure != null)
+        rootContainer.getStorage().write(new WriteOperation()
         {
-          return failure;
-        }
+          @Override
+          public void run(WriteableStorage txn) throws Exception
+          {
+            SortedSet<DN> newBaseDNs = newCfg.getBaseDN();
+            DN[] newBaseDNsArray = newBaseDNs.toArray(new DN[newBaseDNs.size()]);
 
-        baseDNs = newBaseDNsArray;
+            // Check for changes to the base DNs.
+            removeDeletedBaseDNs(newBaseDNs, txn);
+            if (!createNewBaseDNs(newBaseDNsArray, ccr, txn))
+            {
+              return;
+            }
+
+            baseDNs = newBaseDNsArray;
+
+            if(cfg.getDiskFullThreshold() != newCfg.getDiskFullThreshold() ||
+                cfg.getDiskLowThreshold() != newCfg.getDiskLowThreshold())
+            {
+              diskMonitor.setFullThreshold(newCfg.getDiskFullThreshold());
+              diskMonitor.setLowThreshold(newCfg.getDiskLowThreshold());
+            }
+            
+            // Put the new configuration in place.
+            cfg = newCfg;
+          }
+        });
       }
-
-      if(cfg.getDiskFullThreshold() != newCfg.getDiskFullThreshold() ||
-          cfg.getDiskLowThreshold() != newCfg.getDiskLowThreshold())
-      {
-        diskMonitor.setFullThreshold(newCfg.getDiskFullThreshold());
-        diskMonitor.setLowThreshold(newCfg.getDiskLowThreshold());
-      }
-
-      // Put the new configuration in place.
-      this.cfg = newCfg;
     }
     catch (Exception e)
     {
-      messages.add(LocalizableMessage.raw(stackTraceToSingleLineString(e)));
-      return new ConfigChangeResult(
-          DirectoryServer.getServerErrorResultCode(), false, messages);
+      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
+      ccr.addMessage(LocalizableMessage.raw(stackTraceToSingleLineString(e)));
     }
-
-    return new ConfigChangeResult(resultCode, false, messages);
+    return ccr;
   }
 
-  private void removeDeletedBaseDNs(SortedSet<DN> newBaseDNs) throws DirectoryException
+  private void removeDeletedBaseDNs(SortedSet<DN> newBaseDNs, WriteableStorage txn) throws DirectoryException
   {
     for (DN baseDN : cfg.getBaseDN())
     {
@@ -1323,12 +1314,12 @@
         DirectoryServer.deregisterBaseDN(baseDN);
         EntryContainer ec = rootContainer.unregisterEntryContainer(baseDN);
         ec.close();
-        ec.delete();
+        ec.delete(txn);
       }
     }
   }
 
-  private ConfigChangeResult createNewBaseDNs(DN[] newBaseDNsArray, ArrayList<LocalizableMessage> messages)
+  private boolean createNewBaseDNs(DN[] newBaseDNsArray, ConfigChangeResult ccr, WriteableStorage txn)
   {
     for (DN baseDN : newBaseDNsArray)
     {
@@ -1337,7 +1328,7 @@
         try
         {
           // The base DN was added.
-          EntryContainer ec = rootContainer.openEntryContainer(baseDN, null);
+          EntryContainer ec = rootContainer.openEntryContainer(baseDN, null, txn);
           rootContainer.registerEntryContainer(baseDN, ec);
           DirectoryServer.registerBaseDN(baseDN, this, false);
         }
@@ -1345,13 +1336,13 @@
         {
           logger.traceException(e);
 
-          ResultCode resultCode = DirectoryServer.getServerErrorResultCode();
-          messages.add(ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(baseDN, e));
-          return new ConfigChangeResult(resultCode, false, messages);
+          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
+          ccr.addMessage(ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(baseDN, e));
+          return false;
         }
       }
     }
-    return null;
+    return true;
   }
 
   /**
@@ -1380,15 +1371,7 @@
   public RootContainer getReadOnlyRootContainer()
       throws ConfigException, InitializationException
   {
-    EnvironmentConfig envConfig = parseConfigEntry(cfg);
-
-    envConfig.setReadOnly(true);
-    envConfig.setAllowCreate(false);
-    envConfig.setTransactional(false);
-    envConfig.setConfigParam(ENV_IS_LOCKING, "true");
-    envConfig.setConfigParam(ENV_RUN_CHECKPOINTER, "true");
-
-    return initializeRootContainer(envConfig);
+    return initializeRootContainer();
   }
 
   /**
@@ -1397,11 +1380,9 @@
    *
    * @throws  ConfigException  If an unrecoverable problem arises in the
    *                           process of performing the initialization.
-   *
-   * @throws  JebException     If an error occurs while removing the data.
+   * @throws  StorageRuntimeException If an error occurs while removing the data.
    */
-  public void clearBackend()
-      throws ConfigException, JebException
+  public void clearBackend() throws ConfigException, StorageRuntimeException
   {
     // Determine the backend database directory.
     File parentDirectory = getFileForPath(cfg.getDBDirectory());
@@ -1419,7 +1400,7 @@
    */
   DirectoryException createDirectoryException(StorageRuntimeException e)
   {
-    if (true) {
+    if (true) { // FIXME JNR
       throw new NotImplementedException();
     }
     if (/*e instanceof EnvironmentFailureException && */ !rootContainer.isValid()) {
@@ -1465,12 +1446,12 @@
     return cfg.dn();
   }
 
-  private RootContainer initializeRootContainer(EnvironmentConfig envConfig)
+  private RootContainer initializeRootContainer()
           throws ConfigException, InitializationException {
     // Open the database environment
     try {
       RootContainer rc = new RootContainer(this, cfg);
-      rc.open(envConfig);
+      rc.open();
       return rc;
     }
     catch (StorageRuntimeException e)
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java
index d3d7f78..569fa89 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java
@@ -104,7 +104,6 @@
    * @throws StorageRuntimeException
    *           If an error occurs in the JE database.
    */
-  @SuppressWarnings("unchecked")
   DN2URI(TreeName treeName, Storage storage, EntryContainer entryContainer)
       throws StorageRuntimeException
   {
@@ -502,7 +501,7 @@
   {
     if (containsReferrals == ConditionResult.UNDEFINED)
     {
-      containsReferrals = containsReferrals(null);
+      containsReferrals = containsReferrals(txn);
     }
 
     if (containsReferrals == ConditionResult.FALSE)
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseContainer.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseContainer.java
index 612bbc1..0043a69 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseContainer.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseContainer.java
@@ -211,7 +211,7 @@
    * @return The count of key/data pairs in the database.
    * @throws StorageRuntimeException If an error occurs in the JE operation.
    */
-  public long getRecordCount() throws StorageRuntimeException
+  public long getRecordCount(ReadableStorage txn) throws StorageRuntimeException
   {
     long count = treeName.count();
     if (logger.isTraceEnabled())
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseEnvironmentMonitor.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseEnvironmentMonitor.java
new file mode 100644
index 0000000..1d8b46d
--- /dev/null
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseEnvironmentMonitor.java
@@ -0,0 +1,394 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2006-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+package org.opends.server.backends.pluggable;
+
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.opends.server.admin.std.server.MonitorProviderCfg;
+import org.opends.server.api.AttributeSyntax;
+import org.opends.server.api.MonitorProvider;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.*;
+import org.opends.server.util.TimeThread;
+
+/**
+ * A monitor provider for a Berkeley DB JE environment.
+ * It uses reflection on the environment statistics object
+ * so that we don't need to keep a list of all the stats.
+ */
+public class DatabaseEnvironmentMonitor
+       extends MonitorProvider<MonitorProviderCfg>
+{
+  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+  /** Represents the statistical information kept for each search filter. */
+  private static class FilterStats implements Comparable<FilterStats>
+  {
+    private volatile LocalizableMessage failureReason = LocalizableMessage.EMPTY;
+    private long maxMatchingEntries = -1;
+    private final AtomicInteger hits = new AtomicInteger();
+
+    @Override
+    public int compareTo(FilterStats that) {
+      return this.hits.get() - that.hits.get();
+    }
+
+    private void update(int hitCount, LocalizableMessage failureReason)
+    {
+      this.hits.getAndAdd(hitCount);
+      this.failureReason = failureReason;
+    }
+
+    private void update(int hitCount, long matchingEntries)
+    {
+      this.hits.getAndAdd(hitCount);
+      this.failureReason = LocalizableMessage.EMPTY;
+      synchronized(this)
+      {
+        if(matchingEntries > maxMatchingEntries)
+        {
+          maxMatchingEntries = matchingEntries;
+        }
+      }
+    }
+  }
+
+  /** The name of this monitor instance. */
+  private String name;
+
+  /** The root container to be monitored. */
+  private RootContainer rootContainer;
+
+  private int maxEntries = 1024;
+  private boolean filterUseEnabled = false;
+  private String startTimeStamp;
+  private final HashMap<SearchFilter, FilterStats> filterToStats =
+      new HashMap<SearchFilter, FilterStats>();
+  private final AtomicInteger indexedSearchCount = new AtomicInteger();
+  private final AtomicInteger unindexedSearchCount = new AtomicInteger();
+
+  /**
+   * Creates a new database environment monitor.
+   * @param name The monitor instance name.
+   * @param rootContainer A root container handle for the database to be
+   * monitored.
+   */
+  public DatabaseEnvironmentMonitor(String name, RootContainer rootContainer)
+  {
+    this.name = name;
+    this.rootContainer = rootContainer;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void initializeMonitorProvider(MonitorProviderCfg configuration)
+       throws ConfigException, InitializationException
+  {
+  }
+
+  /**
+   * Retrieves the name of this monitor provider.  It should be unique among all
+   * monitor providers, including all instances of the same monitor provider.
+   *
+   * @return The name of this monitor provider.
+   */
+  @Override
+  public String getMonitorInstanceName()
+  {
+    return name;
+  }
+
+  /**
+   * Creates monitor attribute values for a given JE statistics object,
+   * using reflection to call all the getter methods of the statistics object.
+   * The attribute type names of the created attribute values are derived from
+   * the names of the getter methods.
+   * @param monitorAttrs The monitor attribute values are inserted into this
+   * attribute list.
+   * @param stats The JE statistics object.
+   * @param attrPrefix A common prefix for the attribute type names of the
+   * monitor attribute values, to distinguish the attributes of one
+   * type of statistical object from another, and to avoid attribute name
+   * collisions.
+   */
+  private void addAttributesForStatsObject(ArrayList<Attribute> monitorAttrs,
+                                           Object stats, String attrPrefix)
+  {
+    Class<?> c = stats.getClass();
+    Method[] methods = c.getMethods();
+
+    // Iterate through all the statistic class methods.
+    for (Method method : methods)
+    {
+      // Invoke all the getters returning integer values.
+      if (method.getName().startsWith("get"))
+      {
+        Class<?> returnType = method.getReturnType();
+        if (returnType.equals(int.class) || returnType.equals(long.class))
+        {
+          AttributeSyntax<?> integerSyntax =
+               DirectoryServer.getDefaultIntegerSyntax();
+
+          // Remove the 'get' from the method name and add the prefix.
+          String attrName = attrPrefix + method.getName().substring(3);
+
+          try
+          {
+            // Read the statistic.
+            Object statValue = method.invoke(stats);
+
+            // Create an attribute from the statistic.
+            AttributeType attrType =
+                 DirectoryServer.getDefaultAttributeType(attrName, integerSyntax);
+            monitorAttrs.add(Attributes.create(attrType, String.valueOf(statValue)));
+
+          } catch (Exception e)
+          {
+            logger.traceException(e);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Retrieves a set of attributes containing monitor data that should be
+   * returned to the client if the corresponding monitor entry is requested.
+   *
+   * @return A set of attributes containing monitor data that should be
+   *         returned to the client if the corresponding monitor entry is
+   *         requested.
+   */
+  @Override
+  public ArrayList<Attribute> getMonitorData()
+  {
+    ArrayList<Attribute> monitorAttrs = new ArrayList<Attribute>();
+
+    AttributeBuilder needReindex = new AttributeBuilder("need-reindex");
+    for(EntryContainer ec : rootContainer.getEntryContainers())
+    {
+      List<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
+      ec.listDatabases(databases);
+      for(DatabaseContainer dc : databases)
+      {
+        if(dc instanceof Index && !((Index)dc).isTrusted())
+        {
+          needReindex.add(dc.getName().toString());
+        }
+      }
+    }
+    if(needReindex.size() > 0)
+    {
+      monitorAttrs.add(needReindex.toAttribute());
+    }
+
+    if(filterUseEnabled)
+    {
+      monitorAttrs.add(Attributes.create("filter-use-startTime",
+          startTimeStamp));
+      AttributeBuilder builder = new AttributeBuilder("filter-use");
+
+      StringBuilder stringBuilder = new StringBuilder();
+      synchronized(filterToStats)
+      {
+        for(Map.Entry<SearchFilter, FilterStats> entry :
+            filterToStats.entrySet())
+        {
+          entry.getKey().toString(stringBuilder);
+          stringBuilder.append(" hits:");
+          stringBuilder.append(entry.getValue().hits.get());
+          stringBuilder.append(" maxmatches:");
+          stringBuilder.append(entry.getValue().maxMatchingEntries);
+          stringBuilder.append(" message:");
+          stringBuilder.append(entry.getValue().failureReason);
+          builder.add(stringBuilder.toString());
+          stringBuilder.setLength(0);
+        }
+      }
+      monitorAttrs.add(builder.toAttribute());
+      monitorAttrs.add(Attributes.create("filter-use-indexed",
+          String.valueOf(indexedSearchCount.get())));
+      monitorAttrs.add(Attributes.create("filter-use-unindexed",
+          String.valueOf(unindexedSearchCount.get())));
+    }
+
+    return monitorAttrs;
+  }
+
+
+  /**
+   * Updates the index filter statistics with this latest search filter
+   * and the reason why an index was not used.
+   *
+   * @param searchFilter The search filter that was evaluated.
+   * @param failureMessage The reason why an index was not used.
+   */
+  public void updateStats(SearchFilter searchFilter, LocalizableMessage failureMessage)
+  {
+    if(!filterUseEnabled)
+    {
+      return;
+    }
+
+    FilterStats stats;
+    synchronized(filterToStats)
+    {
+      stats = filterToStats.get(searchFilter);
+
+
+      if(stats != null)
+      {
+        stats.update(1, failureMessage);
+      }
+      else
+      {
+        stats = new FilterStats();
+        stats.update(1, failureMessage);
+        removeLowestHit();
+        filterToStats.put(searchFilter, stats);
+      }
+    }
+  }
+
+  /**
+   * Updates the index filter statistics with this latest search filter
+   * and the number of entries matched by the index lookup.
+   *
+   * @param searchFilter The search filter that was evaluated.
+   * @param matchingEntries The number of entries matched by the successful
+   *                        index lookup.
+   */
+  public void updateStats(SearchFilter searchFilter, long matchingEntries)
+  {
+    if(!filterUseEnabled)
+    {
+      return;
+    }
+
+    FilterStats stats;
+    synchronized(filterToStats)
+    {
+      stats = filterToStats.get(searchFilter);
+
+
+      if(stats != null)
+      {
+        stats.update(1, matchingEntries);
+      }
+      else
+      {
+        stats = new FilterStats();
+        stats.update(1, matchingEntries);
+        removeLowestHit();
+        filterToStats.put(searchFilter, stats);
+      }
+    }
+  }
+
+  /**
+   * Enable or disable index filter statistics gathering.
+   *
+   * @param enabled <code>true></code> to enable index filter statics gathering.
+   */
+  public void enableFilterUseStats(boolean enabled)
+  {
+    if(enabled && !filterUseEnabled)
+    {
+      startTimeStamp = TimeThread.getGMTTime();
+      indexedSearchCount.set(0);
+      unindexedSearchCount.set(0);
+    }
+    else if(!enabled)
+    {
+      filterToStats.clear();
+    }
+    filterUseEnabled = enabled;
+  }
+
+  /**
+   * Indicates if index filter statistics gathering is enabled.
+   *
+   * @return <code>true</code> If index filter statistics gathering is enabled.
+   */
+  public boolean isFilterUseEnabled()
+  {
+    return filterUseEnabled;
+  }
+
+  /**
+   * Sets the maximum number of search filters statistics entries to keep
+   * before ones with the least hits will be removed.
+   *
+   * @param maxEntries The maximum number of search filters statistics
+   * entries to keep
+   */
+  public void setMaxEntries(int maxEntries) {
+    this.maxEntries = maxEntries;
+  }
+
+  /**
+   * Updates the statistics counter to include an indexed search.
+   */
+  public void updateIndexedSearchCount()
+  {
+    indexedSearchCount.getAndIncrement();
+  }
+
+  /**
+   * Updates the statistics counter to include an unindexed search.
+   */
+  public void updateUnindexedSearchCount()
+  {
+    unindexedSearchCount.getAndIncrement();
+  }
+
+  private void removeLowestHit()
+  {
+    while(!filterToStats.isEmpty() && filterToStats.size() > maxEntries)
+    {
+      Iterator<Map.Entry<SearchFilter, FilterStats>> i =
+          filterToStats.entrySet().iterator();
+      Map.Entry<SearchFilter, FilterStats> lowest = i.next();
+      Map.Entry<SearchFilter, FilterStats> entry;
+      while(lowest.getValue().hits.get() > 1 && i.hasNext())
+      {
+        entry = i.next();
+        if(entry.getValue().hits.get() < lowest.getValue().hits.get())
+        {
+          lowest = entry;
+        }
+      }
+
+      filterToStats.remove(lowest.getKey());
+    }
+  }
+}
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java
index 35716bd..8ad27f7 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java
@@ -27,7 +27,13 @@
  */
 package org.opends.server.backends.pluggable;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
@@ -60,13 +66,38 @@
 import org.opends.server.backends.pluggable.BackendImpl.TreeName;
 import org.opends.server.backends.pluggable.BackendImpl.WriteOperation;
 import org.opends.server.backends.pluggable.BackendImpl.WriteableStorage;
-import org.opends.server.backends.pluggable.SuffixContainer;
-import org.opends.server.controls.*;
-import org.opends.server.core.*;
-import org.opends.server.types.*;
+import org.opends.server.controls.PagedResultsControl;
+import org.opends.server.controls.ServerSideSortRequestControl;
+import org.opends.server.controls.ServerSideSortResponseControl;
+import org.opends.server.controls.SubtreeDeleteControl;
+import org.opends.server.controls.VLVRequestControl;
+import org.opends.server.core.AddOperation;
+import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.CanceledOperationException;
+import org.opends.server.types.ConfigChangeResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.Operation;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.RDN;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SortKey;
+import org.opends.server.types.VirtualAttributeRule;
 import org.opends.server.util.ServerConstants;
 import org.opends.server.util.StaticUtils;
 
+import com.sleepycat.je.TransactionConfig;
+
 import static org.opends.messages.JebMessages.*;
 import static org.opends.server.backends.pluggable.JebFormat.*;
 import static org.opends.server.core.DirectoryServer.*;
@@ -156,6 +187,8 @@
     {
       try
       {
+        // FIXME this should be a read operation, but I cannot change it
+        // because of AttributeIndex ctor.
         storage.write(new WriteOperation()
         {
           @Override
@@ -216,30 +249,35 @@
 
     /** {@inheritDoc} */
     @Override
-    public ConfigChangeResult applyConfigurationDelete(LocalDBIndexCfg cfg)
+    public ConfigChangeResult applyConfigurationDelete(final LocalDBIndexCfg cfg)
     {
-      boolean adminActionRequired = false;
-      ArrayList<LocalizableMessage> messages = new ArrayList<LocalizableMessage>();
+      final ConfigChangeResult ccr = new ConfigChangeResult();
 
       exclusiveLock.lock();
       try
       {
-        AttributeIndex index = attrIndexMap.get(cfg.getAttribute());
-        deleteAttributeIndex(index);
-        attrIndexMap.remove(cfg.getAttribute());
+        storage.write(new WriteOperation()
+        {
+          @Override
+          public void run(WriteableStorage txn) throws Exception
+          {
+            AttributeIndex index = attrIndexMap.get(cfg.getAttribute());
+            deleteAttributeIndex(txn, index, ccr);
+            attrIndexMap.remove(cfg.getAttribute());
+          }
+        });
       }
-      catch(StorageRuntimeException de)
+      catch (Exception de)
       {
-        messages.add(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(de)));
-        return new ConfigChangeResult(
-            DirectoryServer.getServerErrorResultCode(), adminActionRequired, messages);
+        ccr.setResultCode(getServerErrorResultCode());
+        ccr.addMessage(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(de)));
       }
       finally
       {
         exclusiveLock.unlock();
       }
 
-      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired, messages);
+      return ccr;
     }
   }
 
@@ -354,31 +392,33 @@
 
     /** {@inheritDoc} */
     @Override
-    public ConfigChangeResult applyConfigurationDelete(LocalDBVLVIndexCfg cfg)
+    public ConfigChangeResult applyConfigurationDelete(final LocalDBVLVIndexCfg cfg)
     {
-      boolean adminActionRequired = false;
-      List<LocalizableMessage> messages = new ArrayList<LocalizableMessage>();
-
+      final ConfigChangeResult ccr = new ConfigChangeResult();
       exclusiveLock.lock();
       try
       {
-        VLVIndex vlvIndex =
-          vlvIndexMap.get(cfg.getName().toLowerCase());
-        deleteDatabase(vlvIndex);
-        vlvIndexMap.remove(cfg.getName());
+        storage.write(new WriteOperation()
+        {
+          @Override
+          public void run(WriteableStorage txn) throws Exception
+          {
+            VLVIndex vlvIndex = vlvIndexMap.get(cfg.getName().toLowerCase());
+            deleteDatabase(txn, vlvIndex);
+            vlvIndexMap.remove(cfg.getName());
+          }
+        });
       }
-      catch(StorageRuntimeException de)
+      catch (Exception e)
       {
-        messages.add(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(de)));
-        return new ConfigChangeResult(
-            DirectoryServer.getServerErrorResultCode(), adminActionRequired, messages);
+        ccr.setResultCode(getServerErrorResultCode());
+        ccr.addMessage(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(e)));
       }
       finally
       {
         exclusiveLock.unlock();
       }
-
-      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired, messages);
+      return ccr;
     }
 
   }
@@ -403,7 +443,7 @@
    * @param rootContainer The root container this entry container is in.
    * @throws ConfigException if a configuration related error occurs.
    */
-  public EntryContainer(DN baseDN, String databasePrefix, Backend<?> backend,
+  public EntryContainer(DN baseDN, TreeName databasePrefix, Backend<?> backend,
       LocalDBBackendCfg config, Storage env, RootContainer rootContainer)
           throws ConfigException
   {
@@ -412,8 +452,7 @@
     this.config = config;
     this.storage = env;
     this.rootContainer = rootContainer;
-
-    this.databasePrefix = preparePrefix(databasePrefix);
+    this.databasePrefix = databasePrefix;
 
     config.addLocalDBChangeListener(this);
 
@@ -1316,14 +1355,12 @@
     boolean manageDsaIT = isManageDsaITOperation(searchOperation);
     boolean continueSearch = true;
 
-    // Set the starting value.
-    EntryID begin = null;
     if (pageRequest != null && pageRequest.getCookie().length() != 0)
     {
       // The cookie contains the ID of the next entry to be returned.
       try
       {
-        begin = new EntryID(pageRequest.getCookie());
+        new EntryID(pageRequest.getCookie());
       }
       catch (Exception e)
       {
@@ -1354,10 +1391,8 @@
     // Iterate through the index candidates.
     if (continueSearch)
     {
-      for (Iterator<EntryID> it = entryIDList.iterator(begin); it.hasNext();)
+      for (EntryID id : entryIDList)
       {
-        final EntryID id = it.next();
-
         Entry entry;
         try
         {
@@ -1556,7 +1591,7 @@
                 EntryID nodeID = dn2id.get(txn, dn, false);
                 if (nodeID == null)
                 {
-                  throw new JebException(ERR_JEB_MISSING_DN2ID_RECORD.get(dn));
+                  throw new StorageRuntimeException(ERR_JEB_MISSING_DN2ID_RECORD.get(dn).toString());
                 }
 
                 // Insert into id2subtree for this node.
@@ -1816,7 +1851,7 @@
       DN targetDN,
       ByteSequence leafDNKey,
       EntryID leafID)
-  throws StorageRuntimeException, DirectoryException, JebException
+  throws StorageRuntimeException, DirectoryException
   {
     if(leafID == null || leafDNKey == null)
     {
@@ -1884,7 +1919,7 @@
       EntryID parentID = dn2id.get(txn, parentDN, false);
       if (parentID == null)
       {
-        throw new JebException(ERR_JEB_MISSING_DN2ID_RECORD.get(parentDN));
+        throw new StorageRuntimeException(ERR_JEB_MISSING_DN2ID_RECORD.get(parentDN).toString());
       }
 
       ByteString parentIDBytes = parentID.toByteString();
@@ -2747,14 +2782,12 @@
    * @return The number of entries stored in this entry container.
    * @throws StorageRuntimeException If an error occurs in the JE database.
    */
-  @Override
-  public long getEntryCount() throws StorageRuntimeException
+  public long getEntryCount(ReadableStorage txn) throws StorageRuntimeException
   {
-    EntryID entryID = dn2id.get(null, baseDN, false);
+    final EntryID entryID = dn2id.get(txn, baseDN, false);
     if (entryID != null)
     {
-      EntryIDSet entryIDSet = id2subtree.readKey(entryID.toByteString(), null);
-
+      final EntryIDSet entryIDSet = id2subtree.readKey(entryID.toByteString(), txn);
       long count = entryIDSet.size();
       if(count != Long.MAX_VALUE)
       {
@@ -2764,7 +2797,7 @@
       else
       {
         // The count is not maintained. Fall back to the slow method
-        return id2entry.getRecordCount();
+        return id2entry.getRecordCount(txn);
       }
     }
     else
@@ -2910,36 +2943,14 @@
    * @throws StorageRuntimeException If an error occurs while removing the entry
    *                           container.
    */
-  public void delete() throws StorageRuntimeException
+  public void delete(WriteableStorage txn) throws StorageRuntimeException
   {
     List<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
     listDatabases(databases);
 
-    if(storage.getConfig().getTransactional())
+    for (DatabaseContainer db : databases)
     {
-      Transaction txn = beginTransaction();
-
-      try
-      {
-        for(DatabaseContainer db : databases)
-        {
-          storage.removeDatabase(txn, db.getName());
-        }
-
-        transactionCommit(txn);
-      }
-      catch(StorageRuntimeException de)
-      {
-        transactionAbort(txn);
-        throw de;
-      }
-    }
-    else
-    {
-      for(DatabaseContainer db : databases)
-      {
-        storage.removeDatabase(null, db.getName());
-      }
+      storage.removeDatabase(txn, db.getName());
     }
   }
 
@@ -2950,8 +2961,7 @@
    * @throws StorageRuntimeException If an error occurs while attempting to delete the
    * database.
    */
-  public void deleteDatabase(DatabaseContainer database)
-  throws StorageRuntimeException
+  public void deleteDatabase(WriteableStorage txn, DatabaseContainer database) throws StorageRuntimeException
   {
     if(database == state)
     {
@@ -2960,31 +2970,10 @@
     }
 
     database.close();
-    if(storage.getConfig().getTransactional())
+    storage.removeDatabase(txn, database.getName());
+    if(database instanceof Index)
     {
-      Transaction txn = beginTransaction();
-      try
-      {
-        storage.removeDatabase(txn, database.getName());
-        if(database instanceof Index)
-        {
-          state.removeIndexTrustState(txn, database);
-        }
-        transactionCommit(txn);
-      }
-      catch(StorageRuntimeException de)
-      {
-        transactionAbort(txn);
-        throw de;
-      }
-    }
-    else
-    {
-      storage.removeDatabase(null, database.getName());
-      if(database instanceof Index)
-      {
-        state.removeIndexTrustState(null, database);
-      }
+      state.removeIndexTrustState(txn, database);
     }
   }
 
@@ -2995,31 +2984,14 @@
    * @throws StorageRuntimeException If an JE database error occurs while attempting
    * to delete the index.
    */
-  private void deleteAttributeIndex(AttributeIndex attributeIndex)
+  private void deleteAttributeIndex(WriteableStorage txn, AttributeIndex attributeIndex, ConfigChangeResult ccr)
       throws StorageRuntimeException
   {
     attributeIndex.close();
-    Transaction txn = storage.getConfig().getTransactional()
-      ? beginTransaction() : null;
-    try
+    for (Index index : attributeIndex.getAllIndexes())
     {
-      for (Index index : attributeIndex.getAllIndexes())
-      {
-        storage.removeDatabase(txn, index.getName());
-        state.removeIndexTrustState(txn, index);
-      }
-      if (txn != null)
-      {
-        transactionCommit(txn);
-      }
-    }
-    catch(StorageRuntimeException de)
-    {
-      if (txn != null)
-      {
-        transactionAbort(txn);
-      }
-      throw de;
+      storage.removeDatabase(txn, index.getName());
+      state.removeIndexTrustState(txn, index);
     }
   }
 
@@ -3041,16 +3013,13 @@
    *
    * @param newDatabasePrefix The new database prefix to use.
    * @throws StorageRuntimeException If an error occurs in the JE database.
-   * @throws JebException If an error occurs in the JE backend.
    */
-  public void setDatabasePrefix(String newDatabasePrefix)
-  throws StorageRuntimeException, JebException
-
+  public void setDatabasePrefix(TreeName newDatabasePrefix) throws StorageRuntimeException, StorageRuntimeException
   {
-    List<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
+    final List<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
     listDatabases(databases);
 
-    TreeName newDbPrefix = preparePrefix(newDatabasePrefix);
+    final TreeName newDbPrefix = newDatabasePrefix;
 
     // close the containers.
     for(DatabaseContainer db : databases)
@@ -3060,11 +3029,10 @@
 
     try
     {
-      if(storage.getConfig().getTransactional())
+      storage.write(new WriteOperation()
       {
-        //Rename under transaction
-        Transaction txn = beginTransaction();
-        try
+        @Override
+        public void run(WriteableStorage txn) throws Exception
         {
           for(DatabaseContainer db : databases)
           {
@@ -3072,52 +3040,60 @@
             String newName = oldName.replace(databasePrefix, newDbPrefix);
             storage.renameDatabase(txn, oldName, newName);
           }
-
-          transactionCommit(txn);
-
-          for(DatabaseContainer db : databases)
+        }
+      });
+      storage.write(new WriteOperation()
+      {
+        @Override
+        public void run(WriteableStorage txn) throws Exception
+        {
+          for (DatabaseContainer db : databases)
           {
             TreeName oldName = db.getName();
             String newName = oldName.replace(databasePrefix, newDbPrefix);
             db.setName(newName);
           }
 
-          // Update the prefix.
-          this.databasePrefix = newDbPrefix;
+          databasePrefix = newDbPrefix;
         }
-        catch(Exception e)
-        {
-          transactionAbort(txn);
-
-          String msg = e.getMessage();
-          if (msg == null)
-          {
-            msg = stackTraceToSingleLineString(e);
-          }
-          LocalizableMessage message = ERR_JEB_UNCHECKED_EXCEPTION.get(msg);
-          throw new JebException(message, e);
-        }
-      }
-      else
+      });
+    }
+    catch (Exception e)
+    {
+      String msg = e.getMessage();
+      if (msg == null)
       {
-        for(DatabaseContainer db : databases)
-        {
-          TreeName oldName = db.getName();
-          String newName = oldName.replace(databasePrefix, newDbPrefix);
-          storage.renameDatabase(txn, oldName, newName);
-          db.setName(newName);
-        }
-
-        // Update the prefix.
-        this.databasePrefix = newDbPrefix;
+        msg = stackTraceToSingleLineString(e);
       }
+      LocalizableMessage message = ERR_JEB_UNCHECKED_EXCEPTION.get(msg);
+      throw new StorageRuntimeException(message.toString(), e);
     }
     finally
     {
-      // Open the containers backup.
-      for(DatabaseContainer db : databases)
+      try
       {
-        db.open(txn);
+        storage.write(new WriteOperation()
+        {
+          @Override
+          public void run(WriteableStorage txn) throws Exception
+          {
+            // Open the containers backup.
+            for(DatabaseContainer db : databases)
+            {
+              db.open(txn);
+            }
+          }
+        });
+      }
+      catch (Exception e)
+      {
+        String msg = e.getMessage();
+        if (msg == null)
+        {
+          msg = stackTraceToSingleLineString(e);
+        }
+        LocalizableMessage message = ERR_JEB_UNCHECKED_EXCEPTION.get(msg);
+        throw new StorageRuntimeException(message.toString(), e);
       }
     }
   }
@@ -3232,19 +3208,6 @@
   }
 
   /**
-   * Get the environment config of the JE environment used in this entry
-   * container.
-   *
-   * @return The environment config of the JE environment.
-   * @throws StorageRuntimeException If an error occurs while retrieving the
-   *                           configuration object.
-   */
-  public EnvironmentConfig getEnvironmentConfig() throws StorageRuntimeException
-  {
-    return storage.getConfig();
-  }
-
-  /**
    * Clear the contents of this entry container.
    *
    * @return The number of records deleted.
@@ -3253,7 +3216,28 @@
    */
   public long clear() throws StorageRuntimeException
   {
-    List<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
+    final AtomicLong count = new AtomicLong();
+    try
+    {
+      storage.write(new WriteOperation()
+      {
+        @Override
+        public void run(WriteableStorage txn) throws Exception
+        {
+          count.set(clear0(txn));
+        }
+      });
+      return count.get();
+    }
+    catch (Exception e)
+    {
+      throw new StorageRuntimeException(e);
+    }
+  }
+
+  private long clear0(WriteableStorage txn) throws StorageRuntimeException
+  {
+    final List<DatabaseContainer> databases = new ArrayList<DatabaseContainer>();
     listDatabases(databases);
     long count = 0;
 
@@ -3263,31 +3247,9 @@
     }
     try
     {
-      if(storage.getConfig().getTransactional())
+      for (DatabaseContainer db : databases)
       {
-        Transaction txn = beginTransaction();
-
-        try
-        {
-          for(DatabaseContainer db : databases)
-          {
-            count += storage.truncateDatabase(txn, db.getName(), true);
-          }
-
-          transactionCommit(txn);
-        }
-        catch(StorageRuntimeException de)
-        {
-          transactionAbort(txn);
-          throw de;
-        }
-      }
-      else
-      {
-        for(DatabaseContainer db : databases)
-        {
-          count += storage.truncateDatabase(null, db.getName(), true);
-        }
+        count += storage.truncateDatabase(txn, db.getName(), true);
       }
     }
     finally
@@ -3297,39 +3259,11 @@
         db.open(txn);
       }
 
-      Transaction txn = null;
-      try
+      for (DatabaseContainer db : databases)
       {
-        if(storage.getConfig().getTransactional()) {
-          txn = beginTransaction();
-        }
-        for(DatabaseContainer db : databases)
+        if (db instanceof Index)
         {
-          if (db instanceof Index)
-          {
-            Index index = (Index)db;
-            index.setTrusted(txn, true);
-          }
-        }
-        if(storage.getConfig().getTransactional()) {
-          transactionCommit(txn);
-        }
-      }
-      catch(Exception de)
-      {
-        logger.traceException(de);
-
-        // This is mainly used during the unit tests, so it's not essential.
-        try
-        {
-          if (txn != null)
-          {
-            transactionAbort(txn);
-          }
-        }
-        catch (Exception e)
-        {
-          logger.traceException(de);
+          ((Index) db).setTrusted(txn, true);
         }
       }
     }
@@ -3343,34 +3277,30 @@
    * @param database The database to clear.
    * @throws StorageRuntimeException if a JE database error occurs.
    */
-  public void clearDatabase(DatabaseContainer database)
-  throws StorageRuntimeException
+  public void clearDatabase(final DatabaseContainer database) throws StorageRuntimeException
   {
     database.close();
     try
     {
-      if(storage.getConfig().getTransactional())
+      storage.write(new WriteOperation()
       {
-        Transaction txn = beginTransaction();
-        try
+        @Override
+        public void run(WriteableStorage txn) throws Exception
         {
-          storage.removeDatabase(txn, database.getName());
-          transactionCommit(txn);
+          try
+          {
+            storage.removeDatabase(txn, database.getName());
+          }
+          finally
+          {
+            database.open(txn);
+          }
         }
-        catch(StorageRuntimeException de)
-        {
-          transactionAbort(txn);
-          throw de;
-        }
-      }
-      else
-      {
-        storage.removeDatabase(null, database.getName());
-      }
+      });
     }
-    finally
+    catch (Exception e)
     {
-      database.open(txn);
+      throw new StorageRuntimeException(e);
     }
     if(logger.isTraceEnabled())
     {
@@ -3507,31 +3437,6 @@
     return baseEntry;
   }
 
-
-  /**
-   * Transform a database prefix string to one usable by the DB.
-   * @param databasePrefix the database prefix
-   * @return a new string when non letter or digit characters
-   *         have been replaced with underscore
-   */
-  private TreeName preparePrefix(String databasePrefix)
-  {
-    StringBuilder builder = new StringBuilder(databasePrefix.length());
-    for (int i = 0; i < databasePrefix.length(); i++)
-    {
-      char ch = databasePrefix.charAt(i);
-      if (Character.isLetterOrDigit(ch))
-      {
-        builder.append(ch);
-      }
-      else
-      {
-        builder.append('_');
-      }
-    }
-    return TreeName.of(builder.toString());
-  }
-
   /** Get the exclusive lock. */
   public void lock() {
     exclusiveLock.lock();
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryIDSetSorter.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryIDSetSorter.java
index b77d737..6febd17 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryIDSetSorter.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryIDSetSorter.java
@@ -35,7 +35,6 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SearchScope;
-import org.opends.server.backends.pluggable.SuffixContainer;
 import org.opends.server.controls.VLVRequestControl;
 import org.opends.server.controls.VLVResponseControl;
 import org.opends.server.core.DirectoryServer;
@@ -68,7 +67,7 @@
    *
    * @throws  DirectoryException  If an error occurs while performing the sort.
    */
-  public static EntryIDSet sort(SuffixContainer suffixContainer,
+  public static EntryIDSet sort(EntryContainer suffixContainer,
                                 EntryIDSet entryIDSet,
                                 SearchOperation searchOperation,
                                 SortOrder sortOrder,
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EnvManager.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EnvManager.java
index 2df136f..caae758 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EnvManager.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EnvManager.java
@@ -25,14 +25,16 @@
  *      Portions Copyright 2014 ForgeRock AS
  */
 package org.opends.server.backends.pluggable;
-import org.forgerock.i18n.LocalizableMessage;
-
-import org.forgerock.i18n.slf4j.LocalizedLogger;
-import static org.opends.messages.JebMessages.*;
 
 import java.io.File;
 import java.io.FilenameFilter;
 
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.opends.server.backends.pluggable.BackendImpl.StorageRuntimeException;
+
+import static org.opends.messages.JebMessages.*;
+
 /**
  * A singleton class to manage the life-cycle of a JE database environment.
  */
@@ -40,10 +42,7 @@
 {
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
-
-  /**
-   * A filename filter to match all kinds of JE files.
-   */
+  /** A filename filter to match all kinds of JE files. */
   private static final FilenameFilter jeAllFilesFilter;
 
   static
@@ -53,6 +52,7 @@
     // here but is not public.
     jeAllFilesFilter = new FilenameFilter()
     {
+      @Override
       public boolean accept(File d, String name)
       {
         return name.endsWith(".jdb") ||
@@ -68,10 +68,9 @@
    * The environment must not be open.
    *
    * @param homeDir The backend home directory.
-   * @throws JebException If an error occurs in the JE backend.
+   * @throws StorageRuntimeException If an error occurs in the JE backend.
    */
-  public static void createHomeDir(String homeDir)
-       throws JebException
+  public static void createHomeDir(String homeDir) throws StorageRuntimeException
   {
     File dir = new File(homeDir);
 
@@ -80,7 +79,7 @@
       if (!dir.isDirectory())
       {
         LocalizableMessage message = ERR_JEB_DIRECTORY_INVALID.get(homeDir);
-        throw new JebException(message);
+        throw new StorageRuntimeException(message.toString());
       }
       removeFiles(homeDir);
     }
@@ -94,7 +93,7 @@
       {
         logger.traceException(e);
         LocalizableMessage message = ERR_JEB_CREATE_FAIL.get(e.getMessage());
-        throw new JebException(message, e);
+        throw new StorageRuntimeException(message.toString(), e);
       }
     }
   }
@@ -104,22 +103,22 @@
    * The environment must not be open.
    *
    * @param homeDir The backend home directory
-   * @throws JebException If an error occurs in the JE backend or if the
-   * specified home directory does not exist.
+   * @throws StorageRuntimeException
+   *           If an error occurs in the JE backend or if the specified home
+   *           directory does not exist.
    */
-  public static void removeFiles(String homeDir)
-       throws JebException
+  public static void removeFiles(String homeDir) throws StorageRuntimeException
   {
     File dir = new File(homeDir);
     if (!dir.exists())
     {
       LocalizableMessage message = ERR_JEB_DIRECTORY_DOES_NOT_EXIST.get(homeDir);
-      throw new JebException(message);
+      throw new StorageRuntimeException(message.toString());
     }
     if (!dir.isDirectory())
     {
       LocalizableMessage message = ERR_JEB_DIRECTORY_INVALID.get(homeDir);
-      throw new JebException(message);
+      throw new StorageRuntimeException(message.toString());
     }
 
     try
@@ -134,7 +133,7 @@
     {
       logger.traceException(e);
       LocalizableMessage message = ERR_JEB_REMOVE_FAIL.get(e.getMessage());
-      throw new JebException(message, e);
+      throw new StorageRuntimeException(message.toString(), e);
     }
   }
 
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/ExportJob.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/ExportJob.java
index 739fb30..b7f03e3 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/ExportJob.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/ExportJob.java
@@ -88,12 +88,11 @@
    * @param rootContainer The root container to export.
    * @throws StorageRuntimeException 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(RootContainer rootContainer)
-       throws IOException, LDIFException, StorageRuntimeException, JebException
+       throws IOException, LDIFException, StorageRuntimeException
   {
     List<DN> includeBranches = exportConfig.getIncludeBranches();
     DN baseDN;
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/Index.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/Index.java
index 257ff78..f26d573 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/Index.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/Index.java
@@ -136,7 +136,7 @@
     this.maintainCount = maintainCount;
 
     this.state = state;
-    this.trusted = state.getIndexTrustState(null, this);
+    this.trusted = state.getIndexTrustState(txn, this);
     if (!trusted && entryContainer.getHighestEntryID(txn).longValue() == 0)
     {
       // If there are no entries in the entry container then there
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/IndexFilter.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/IndexFilter.java
index d192e15..40a28b3 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/IndexFilter.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/IndexFilter.java
@@ -33,7 +33,6 @@
 
 import org.opends.server.backends.pluggable.AttributeIndex.IndexFilterType;
 import org.opends.server.core.SearchOperation;
-import org.opends.server.monitors.DatabaseEnvironmentMonitor;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.FilterType;
 import org.opends.server.types.SearchFilter;
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/JebException.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/JebException.java
deleted file mode 100644
index 1329cfa..0000000
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/JebException.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at legal-notices/CDDLv1_0.txt.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Copyright 2006-2009 Sun Microsystems, Inc.
- *      Portions Copyright 2014 ForgeRock AS
- */
-package org.opends.server.backends.pluggable;
-
-
-
-import org.opends.server.types.IdentifiedException;
-import org.forgerock.i18n.LocalizableMessage;
-
-
-/**
- * This class defines an exception that may be thrown if a problem occurs in the
- * JE backend database.
- */
-public class JebException
-     extends IdentifiedException
-{
-  /**
-   * The serial version identifier required to satisfy the compiler because this
-   * class extends <CODE>java.lang.Exception</CODE>, which implements the
-   * <CODE>java.io.Serializable</CODE> interface.  This value was generated
-   * using the <CODE>serialver</CODE> command-line utility included with the
-   * Java SDK.
-   */
-  static final long serialVersionUID = 3110979454298870834L;
-
-
-
-  /**
-   * Creates a new JE backend exception.
-   */
-  public JebException()
-  {
-    super();
-  }
-
-
-
-  /**
-   * Creates a new JE backend exception with the provided message.
-   *
-   * @param  message    The message that explains the problem that occurred.
-   */
-  public JebException(LocalizableMessage message)
-  {
-    super(message);
-  }
-
-
-
-  /**
-   * Creates a new JE backend exception with the provided message and root
-   * cause.
-   *
-   * @param  message    The message that explains the problem that occurred.
-   * @param  cause      The exception that was caught to trigger this exception.
-   */
-  public JebException(LocalizableMessage message, Throwable cause)
-  {
-    super(message, cause);
-  }
-
-
-
-}
-
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/NullIndex.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/NullIndex.java
index d4b5a13..fd13bb9 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/NullIndex.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/NullIndex.java
@@ -251,7 +251,7 @@
 
   /** {@inheritDoc} */
   @Override
-  public long getRecordCount() throws StorageRuntimeException
+  public long getRecordCount(ReadableStorage txn) throws StorageRuntimeException
   {
     return 0;
   }
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/RootContainer.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/RootContainer.java
index efc4b12..804c101 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/RootContainer.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/RootContainer.java
@@ -34,16 +34,19 @@
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.opendj.config.server.ConfigException;
-import org.forgerock.opendj.ldap.ResultCode;
 import org.opends.server.admin.server.ConfigurationChangeListener;
 import org.opends.server.admin.std.server.LocalDBBackendCfg;
 import org.opends.server.api.Backend;
-import org.opends.server.backends.pluggable.BackendImpl.Storage;
+import org.opends.server.api.CompressedSchema;
+import org.opends.server.backends.persistit.PersistItStorage;
+import org.opends.server.backends.pluggable.BackendImpl.ReadOperation;
+import org.opends.server.backends.pluggable.BackendImpl.ReadableStorage;
 import org.opends.server.backends.pluggable.BackendImpl.StorageRuntimeException;
+import org.opends.server.backends.pluggable.BackendImpl.TreeName;
 import org.opends.server.backends.pluggable.BackendImpl.WriteOperation;
 import org.opends.server.backends.pluggable.BackendImpl.WriteableStorage;
+import org.opends.server.core.DefaultCompressedSchema;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.monitors.DatabaseEnvironmentMonitor;
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DN;
 import org.opends.server.types.FilePermission;
@@ -64,10 +67,7 @@
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
   /** The JE database environment. */
-  private Storage storage;
-
-  /** Used to force a checkpoint during import. */
-  private final CheckpointConfig importForceCheckPoint = new CheckpointConfig();
+  private PersistItStorage storage; // FIXME JNR do not hardcode here
 
   /** The backend configuration. */
   private LocalDBBackendCfg config;
@@ -84,8 +84,9 @@
   /** The cached value of the next entry identifier to be assigned. */
   private AtomicLong nextid = new AtomicLong(1);
 
+  // FIXME JNR Switch back to a database persisted implementation of CompressedSchema
   /** The compressed schema manager for this backend. */
-  private JECompressedSchema compressedSchema;
+  private CompressedSchema compressedSchema;
 
 
 
@@ -106,13 +107,16 @@
     getMonitorProvider().setMaxEntries(config.getIndexFilterAnalyzerMaxFilters());
 
     config.addLocalDBChangeListener(this);
-    importForceCheckPoint.setForce(true);
+  }
+
+  PersistItStorage getStorage()
+  {
+    return storage;
   }
 
   /**
    * Opens the root container using the JE configuration object provided.
    *
-   * @param  envConfig               The JE environment configuration.
    * @throws StorageRuntimeException       If a database error occurs when creating
    *                                 the environment.
    * @throws InitializationException If an initialization error occurs while
@@ -120,7 +124,7 @@
    * @throws ConfigException         If an configuration error occurs while
    *                                 creating the environment.
    */
-  public void open(EnvironmentConfig envConfig)
+  public void open()
       throws StorageRuntimeException, InitializationException, ConfigException
   {
     // Determine the backend database directory.
@@ -183,39 +187,26 @@
     }
 
     // Open the database environment
-    storage = new Storage(backendDirectory, envConfig);
+    storage = new PersistItStorage(backendDirectory, this.config);
 
-    if (logger.isTraceEnabled())
+    compressedSchema = new DefaultCompressedSchema();
+    try
     {
-      logger.trace("JE (%s) environment opened with the following config: %n%s",
-          JEVersion.CURRENT_VERSION, storage.getConfig());
-
-      // Get current size of heap in bytes
-      long heapSize = Runtime.getRuntime().totalMemory();
-
-      // Get maximum size of heap in bytes. The heap cannot grow beyond this size.
-      // Any attempt will result in an OutOfMemoryException.
-      long heapMaxSize = Runtime.getRuntime().maxMemory();
-
-      // Get amount of free memory within the heap in bytes. This size will increase
-      // after garbage collection and decrease as new objects are created.
-      long heapFreeSize = Runtime.getRuntime().freeMemory();
-
-      logger.trace("Current size of heap: %d bytes", heapSize);
-      logger.trace("Max size of heap: %d bytes", heapMaxSize);
-      logger.trace("Free memory in heap: %d bytes", heapFreeSize);
-    }
-
-    compressedSchema = new JECompressedSchema(storage);
-
-    storage.write(new WriteOperation()
-    {
-      @Override
-      public void run(WriteableStorage txn) throws Exception
+      storage.initialize(null);
+      storage.open();
+      storage.write(new WriteOperation()
       {
-        openAndRegisterEntryContainers(txn, config.getBaseDN());
-      }
-    });
+        @Override
+        public void run(WriteableStorage txn) throws Exception
+        {
+          openAndRegisterEntryContainers(txn, config.getBaseDN());
+        }
+      });
+    }
+    catch (Exception e)
+    {
+      throw new StorageRuntimeException(e);
+    }
   }
 
   /**
@@ -248,13 +239,26 @@
       databasePrefix = name;
     }
 
-    EntryContainer ec = new EntryContainer(baseDN, databasePrefix,
+    EntryContainer ec = new EntryContainer(baseDN, toSuffixName(databasePrefix),
                                            backend, config, storage, this);
     ec.open(txn);
     return ec;
   }
 
   /**
+   * Transform a database prefix string to one usable by the DB.
+   *
+   * @param databasePrefix
+   *          the database prefix
+   * @return a new string when non letter or digit characters have been replaced
+   *         with underscore
+   */
+  private TreeName toSuffixName(String databasePrefix)
+  {
+    return TreeName.of(storage.toSuffixName(databasePrefix));
+  }
+
+  /**
    * Registers the entry container for a base DN.
    *
    * @param baseDN The base DN of the entry container to close.
@@ -325,7 +329,7 @@
    *
    * @return  The compressed schema manager for this backend.
    */
-  public JECompressedSchema getCompressedSchema()
+  public CompressedSchema getCompressedSchema()
   {
     return compressedSchema;
   }
@@ -537,35 +541,6 @@
   }
 
   /**
-   * Get the environment transaction stats of the JE environment used
-   * in this root container.
-   *
-   * @param statsConfig The configuration to use for the EnvironmentStats
-   *                    object.
-   * @return The environment status of the JE environment.
-   * @throws StorageRuntimeException If an error occurs while retrieving the stats
-   *                           object.
-   */
-  public TransactionStats getEnvironmentTransactionStats(
-      StatsConfig statsConfig) throws StorageRuntimeException
-  {
-    return storage.getTransactionStats(statsConfig);
-  }
-
-  /**
-   * Get the environment config of the JE environment used in this root
-   * container.
-   *
-   * @return The environment config of the JE environment.
-   * @throws StorageRuntimeException If an error occurs while retrieving the
-   *                           configuration object.
-   */
-  public EnvironmentConfig getEnvironmentConfig() throws StorageRuntimeException
-  {
-    return storage.getConfig();
-  }
-
-  /**
    * Get the backend configuration used by this root container.
    *
    * @return The JE backend configuration used by this root container.
@@ -584,21 +559,34 @@
    */
   public long getEntryCount() throws StorageRuntimeException
   {
-    long entryCount = 0;
-    for(EntryContainer ec : this.entryContainers.values())
+    try
     {
-      ec.sharedLock.lock();
-      try
+      return storage.read(new ReadOperation<Long>()
       {
-        entryCount += ec.getEntryCount();
-      }
-      finally
-      {
-        ec.sharedLock.unlock();
-      }
+        @Override
+        public Long run(ReadableStorage txn) throws Exception
+        {
+          long entryCount = 0;
+          for (EntryContainer ec : entryContainers.values())
+          {
+            ec.sharedLock.lock();
+            try
+            {
+              entryCount += ec.getEntryCount(txn);
+            }
+            finally
+            {
+              ec.sharedLock.unlock();
+            }
+          }
+          return entryCount;
+        }
+      });
     }
-
-    return entryCount;
+    catch (Exception e)
+    {
+      throw new StorageRuntimeException(e);
+    }
   }
 
   /**
@@ -705,81 +693,10 @@
   @Override
   public ConfigChangeResult applyConfigurationChange(LocalDBBackendCfg cfg)
   {
-    boolean adminActionRequired = false;
-    ArrayList<LocalizableMessage> messages = new ArrayList<LocalizableMessage>();
+    final ConfigChangeResult ccr = new ConfigChangeResult();
 
     try
     {
-      if(storage != null)
-      {
-        // Check if any JE non-mutable properties were changed.
-        EnvironmentConfig oldEnvConfig = storage.getConfig();
-        EnvironmentConfig newEnvConfig =
-            ConfigurableEnvironment.parseConfigEntry(cfg);
-        Map<?,?> paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
-
-        // Iterate through native JE properties.
-        SortedSet<String> jeProperties = cfg.getJEProperty();
-        for (String jeEntry : jeProperties) {
-          // There is no need to validate properties yet again.
-          StringTokenizer st = new StringTokenizer(jeEntry, "=");
-          if (st.countTokens() == 2) {
-            String jePropertyName = st.nextToken();
-            String jePropertyValue = st.nextToken();
-            ConfigParam param = (ConfigParam) paramsMap.get(jePropertyName);
-            if (!param.isMutable()) {
-              String oldValue = oldEnvConfig.getConfigParam(param.getName());
-              if (!oldValue.equalsIgnoreCase(jePropertyValue)) {
-                adminActionRequired = true;
-                messages.add(INFO_CONFIG_JE_PROPERTY_REQUIRES_RESTART.get(jePropertyName));
-                if(logger.isTraceEnabled()) {
-                  logger.trace("The change to the following property " +
-                    "will take effect when the component is restarted: " +
-                    jePropertyName);
-                }
-              }
-            }
-          }
-        }
-
-        // Iterate through JE configuration attributes.
-        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))
-            {
-              adminActionRequired = true;
-              String configAttr = ConfigurableEnvironment.
-                  getAttributeForProperty(param.getName());
-              if (configAttr != null)
-              {
-                messages.add(NOTE_JEB_CONFIG_ATTR_REQUIRES_RESTART.get(configAttr));
-              }
-              else
-              {
-                messages.add(NOTE_JEB_CONFIG_ATTR_REQUIRES_RESTART.get(param.getName()));
-              }
-              if(logger.isTraceEnabled())
-              {
-                logger.trace("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.
-        storage.setMutableConfig(newEnvConfig);
-
-        logger.trace("JE database configuration: %s", storage.getConfig());
-      }
-
       // Create the directory if it doesn't exist.
       if(!cfg.getDBDirectory().equals(this.config.getDBDirectory()))
       {
@@ -791,31 +708,25 @@
         {
           if(!backendDirectory.mkdirs())
           {
-            messages.add(ERR_JEB_CREATE_FAIL.get(backendDirectory.getPath()));
-            return new ConfigChangeResult(
-                DirectoryServer.getServerErrorResultCode(),
-                adminActionRequired,
-                messages);
+            ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
+            ccr.addMessage(ERR_JEB_CREATE_FAIL.get(backendDirectory.getPath()));
+            return ccr;
           }
         }
         //Make sure the directory is valid.
         else if (!backendDirectory.isDirectory())
         {
-          messages.add(ERR_JEB_DIRECTORY_INVALID.get(backendDirectory.getPath()));
-          return new ConfigChangeResult(
-              DirectoryServer.getServerErrorResultCode(),
-              adminActionRequired,
-              messages);
+          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
+          ccr.addMessage(ERR_JEB_DIRECTORY_INVALID.get(backendDirectory.getPath()));
+          return ccr;
         }
 
-        adminActionRequired = true;
-        messages.add(NOTE_JEB_CONFIG_DB_DIR_REQUIRES_RESTART.get(
-                        this.config.getDBDirectory(), cfg.getDBDirectory()));
+        ccr.setAdminActionRequired(true);
+        ccr.addMessage(NOTE_JEB_CONFIG_DB_DIR_REQUIRES_RESTART.get(this.config.getDBDirectory(), cfg.getDBDirectory()));
       }
 
-      if(!cfg.getDBDirectoryPermissions().equalsIgnoreCase(
-          config.getDBDirectoryPermissions()) ||
-          !cfg.getDBDirectory().equals(this.config.getDBDirectory()))
+      if (!cfg.getDBDirectoryPermissions().equalsIgnoreCase(config.getDBDirectoryPermissions())
+          || !cfg.getDBDirectory().equals(this.config.getDBDirectory()))
       {
         FilePermission backendPermission;
         try
@@ -825,25 +736,19 @@
         }
         catch(Exception e)
         {
-          messages.add(ERR_CONFIG_BACKEND_MODE_INVALID.get(config.dn()));
-          return new ConfigChangeResult(
-              DirectoryServer.getServerErrorResultCode(),
-              adminActionRequired,
-              messages);
+          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
+          ccr.addMessage(ERR_CONFIG_BACKEND_MODE_INVALID.get(config.dn()));
+          return ccr;
         }
 
-        //Make sure the mode will allow the server itself access to
-        //the database
+        // Make sure the mode will allow the server itself access to the database
         if(!backendPermission.isOwnerWritable() ||
             !backendPermission.isOwnerReadable() ||
             !backendPermission.isOwnerExecutable())
         {
-          messages.add(ERR_CONFIG_BACKEND_INSANE_MODE.get(
-              cfg.getDBDirectoryPermissions()));
-          return new ConfigChangeResult(
-              DirectoryServer.getServerErrorResultCode(),
-              adminActionRequired,
-              messages);
+          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
+          ccr.addMessage(ERR_CONFIG_BACKEND_INSANE_MODE.get(cfg.getDBDirectoryPermissions()));
+          return ccr;
         }
 
         // Get the backend database backendDirectory permissions and apply
@@ -866,22 +771,18 @@
         }
       }
 
-      getMonitorProvider().enableFilterUseStats(
-          cfg.isIndexFilterAnalyzerEnabled());
-      getMonitorProvider()
-          .setMaxEntries(cfg.getIndexFilterAnalyzerMaxFilters());
+      getMonitorProvider().enableFilterUseStats(cfg.isIndexFilterAnalyzerEnabled());
+      getMonitorProvider().setMaxEntries(cfg.getIndexFilterAnalyzerMaxFilters());
 
       this.config = cfg;
     }
     catch (Exception e)
     {
-      messages.add(LocalizableMessage.raw(stackTraceToSingleLineString(e)));
-      return new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
-                                   adminActionRequired,
-                                   messages);
+      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
+      ccr.addMessage(LocalizableMessage.raw(stackTraceToSingleLineString(e)));
+      return ccr;
     }
-
-    return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired, messages);
+    return ccr;
   }
 
   /**
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SortValuesSet.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SortValuesSet.java
index 64d3009..30c55a8 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SortValuesSet.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SortValuesSet.java
@@ -99,9 +99,9 @@
    * @param sv The sort values to add.
    * @param types The types of the values to add.
    * @throws DirectoryException If a Directory Server error occurs.
-   * @throws DatabaseException If an error occurs in the JE database.
+   * @throws StorageRuntimeException If an error occurs in the JE database.
    */
-  void add(SortValues sv) throws DirectoryException
+  void add(SortValues sv) throws StorageRuntimeException, DirectoryException
   {
     add(sv.getEntryID(), sv.getValues(), sv.getTypes());
   }
@@ -587,10 +587,8 @@
    * @return The sort values object at the specified index.
    * @throws DirectoryException If a Directory Server error occurs.
    * @throws StorageRuntimeException If an error occurs in the JE database.
-   * @throws JebException If an error occurs in the JE database.
-   **/
-  public SortValues getSortValues(int index)
-      throws JebException, StorageRuntimeException, DirectoryException
+   */
+  public SortValues getSortValues(int index) throws StorageRuntimeException, DirectoryException
   {
     if(entryIDs == null || entryIDs.length == 0)
     {
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SuffixContainer.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SuffixContainer.java
index 8103cf3..3e0a691 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SuffixContainer.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SuffixContainer.java
@@ -75,12 +75,4 @@
    * @return the baseDN that this suffix container is responsible for
    */
   DN getBaseDN();
-
-  /**
-   * Returns the number of entries stored in this suffix container.
-   *
-   * @return the number of entries stored in this suffix container, or -1 if the
-   *         count not be determined
-   */
-  long getEntryCount();
 }
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VLVIndex.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VLVIndex.java
index 8325d22..daf6557 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VLVIndex.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VLVIndex.java
@@ -26,7 +26,6 @@
  */
 package org.opends.server.backends.pluggable;
 
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -197,7 +196,7 @@
     this.comparator = new VLVKeyComparator(orderingRules, ascending);
 
     this.state = state;
-    this.trusted = state.getIndexTrustState(null, this);
+    this.trusted = state.getIndexTrustState(txn, this);
     if (!trusted && entryContainer.getHighestEntryID(txn).longValue() == 0)
     {
       // If there are no entries in the entry container then there
@@ -264,12 +263,10 @@
    * @return True if the entry ID for the entry are added. False if
    *         the entry ID already exists.
    * @throws StorageRuntimeException If an error occurs in the JE database.
-   * @throws org.opends.server.types.DirectoryException If a Directory Server
-   * error occurs.
-   * @throws JebException If an error occurs in the JE backend.
+   * @throws DirectoryException If a Directory Server error occurs.
    */
   public boolean addEntry(WriteableStorage txn, EntryID entryID, Entry entry)
-      throws StorageRuntimeException, DirectoryException, JebException
+      throws StorageRuntimeException, DirectoryException
   {
     return shouldInclude(entry)
         && insertValues(txn, entryID.longValue(), entry);
@@ -470,13 +467,11 @@
    * found.
    * @throws StorageRuntimeException If an error occurs during an operation on a
    * JE database.
-   * @throws JebException If an error occurs during an operation on a
-   * JE database.
    * @throws DirectoryException If a Directory Server error occurs.
    */
   public boolean containsValues(ReadableStorage txn, long entryID,
-      ByteString[] values, AttributeType[] types) throws JebException,
-      StorageRuntimeException, DirectoryException
+      ByteString[] values, AttributeType[] types)
+          throws StorageRuntimeException, DirectoryException
   {
     SortValuesSet valuesSet = getSortValuesSet(txn, entryID, values, types);
     int pos = valuesSet.binarySearch(entryID, values);
@@ -484,7 +479,7 @@
   }
 
   private boolean insertValues(WriteableStorage txn, long entryID, Entry entry)
-      throws JebException, StorageRuntimeException, DirectoryException
+      throws StorageRuntimeException, DirectoryException
   {
     ByteString[] values = getSortValues(entry);
     AttributeType[] types = getSortTypes();
@@ -545,19 +540,6 @@
     return types;
   }
 
-  private boolean getSearchKeyRange(ReadableStorage txn, ByteString key)
-  {
-    Cursor cursor = txn.openCursor(treeName);
-    try
-    {
-      return cursor.positionToKeyOrNext(key);
-    }
-    finally
-    {
-      cursor.close();
-    }
-  }
-
   /**
    * Update the vlvIndex with the specified values to add and delete.
    *
@@ -1279,25 +1261,42 @@
 
   /** {@inheritDoc} */
   @Override
-  public synchronized ConfigChangeResult applyConfigurationChange(
-      LocalDBVLVIndexCfg cfg)
+  public synchronized ConfigChangeResult applyConfigurationChange(final LocalDBVLVIndexCfg cfg)
   {
-    ResultCode resultCode = ResultCode.SUCCESS;
-    boolean adminActionRequired = false;
-    ArrayList<LocalizableMessage> messages = new ArrayList<LocalizableMessage>();
+    try
+    {
+      final ConfigChangeResult ccr = new ConfigChangeResult();
+      storage.write(new WriteOperation()
+      {
+        @Override
+        public void run(WriteableStorage txn) throws Exception
+        {
+          applyConfigurationChange0(txn, cfg, ccr);
+        }
+      });
+      return ccr;
+    }
+    catch (Exception e)
+    {
+      throw new StorageRuntimeException(e);
+    }
+  }
 
+  private synchronized void applyConfigurationChange0(WriteableStorage txn, LocalDBVLVIndexCfg cfg,
+      ConfigChangeResult ccr)
+  {
     // Update base DN only if changed..
     if(!config.getBaseDN().equals(cfg.getBaseDN()))
     {
       this.baseDN = cfg.getBaseDN();
-      adminActionRequired = true;
+      ccr.setAdminActionRequired(true);
     }
 
     // Update scope only if changed.
     if(!config.getScope().equals(cfg.getScope()))
     {
       this.scope = SearchScope.valueOf(cfg.getScope().name());
-      adminActionRequired = true;
+      ccr.setAdminActionRequired(true);
     }
 
     // Update sort set capacity only if changed.
@@ -1309,7 +1308,7 @@
       // Otherwise, we will lazily update the sorted sets.
       if (config.getMaxBlockSize() < cfg.getMaxBlockSize())
       {
-        adminActionRequired = true;
+        ccr.setAdminActionRequired(true);
       }
     }
 
@@ -1319,18 +1318,13 @@
       try
       {
         this.filter = SearchFilter.createFilterFromString(cfg.getFilter());
-        adminActionRequired = true;
+        ccr.setAdminActionRequired(true);
       }
       catch(Exception e)
       {
-        LocalizableMessage msg = ERR_JEB_CONFIG_VLV_INDEX_BAD_FILTER.get(
-                config.getFilter(), treeName,
-                stackTraceToSingleLineString(e));
-        messages.add(msg);
-        if(resultCode == ResultCode.SUCCESS)
-        {
-          resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
-        }
+        ccr.addMessage(ERR_JEB_CONFIG_VLV_INDEX_BAD_FILTER.get(
+            config.getFilter(), treeName, stackTraceToSingleLineString(e)));
+        ccr.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
       }
     }
 
@@ -1361,22 +1355,16 @@
         }
         catch(Exception e)
         {
-          messages.add(ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(sortKeys[i], treeName));
-          if(resultCode == ResultCode.SUCCESS)
-          {
-            resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
-          }
+          ccr.addMessage(ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(sortKeys[i], treeName));
+          ccr.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
         }
 
         AttributeType attrType =
             DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase());
         if(attrType == null)
         {
-          messages.add(ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(sortKeys[i], treeName));
-          if(resultCode == ResultCode.SUCCESS)
-          {
-            resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
-          }
+          ccr.addMessage(ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(sortKeys[i], treeName));
+          ccr.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
         }
         else
         {
@@ -1392,22 +1380,15 @@
       entryContainer.exclusiveLock.lock();
       try
       {
-        storage.write(new WriteOperation()
-        {
-          @Override
-          public void run(WriteableStorage txn) throws Exception
-          {
-            close();
-            open(txn);
-          }
-        });
+        close();
+        open(txn);
       }
-      catch (Exception e)
+      catch (StorageRuntimeException de)
       {
-        messages.add(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(e)));
-        if(resultCode == ResultCode.SUCCESS)
+        ccr.addMessage(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(de)));
+        if (ccr.getResultCode() == ResultCode.SUCCESS)
         {
-          resultCode = DirectoryServer.getServerErrorResultCode();
+          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
         }
       }
       finally
@@ -1415,29 +1396,28 @@
         entryContainer.exclusiveLock.unlock();
       }
 
-      adminActionRequired = true;
+      ccr.setAdminActionRequired(true);
     }
 
 
-    if(adminActionRequired)
+    if (ccr.adminActionRequired())
     {
       trusted = false;
-      messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(treeName));
+      ccr.addMessage(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(treeName));
       try
       {
         state.putIndexTrustState(null, this, false);
       }
       catch(StorageRuntimeException de)
       {
-        messages.add(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(de)));
-        if(resultCode == ResultCode.SUCCESS)
+        ccr.addMessage(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(de)));
+        if (ccr.getResultCode() == ResultCode.SUCCESS)
         {
-          resultCode = DirectoryServer.getServerErrorResultCode();
+          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
         }
       }
     }
 
     this.config = cfg;
-    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
   }
 }
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VerifyJob.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VerifyJob.java
index 5b8ef1e..25bd6a1 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VerifyJob.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VerifyJob.java
@@ -51,7 +51,6 @@
 import org.opends.server.backends.pluggable.BackendImpl.Cursor;
 import org.opends.server.backends.pluggable.BackendImpl.ReadOperation;
 import org.opends.server.backends.pluggable.BackendImpl.ReadableStorage;
-import org.opends.server.backends.pluggable.BackendImpl.Storage;
 import org.opends.server.backends.pluggable.BackendImpl.StorageRuntimeException;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.types.Attribute;
@@ -146,16 +145,14 @@
    * @param statEntry Optional statistics entry.
    * @return The error count.
    * @throws StorageRuntimeException If an error occurs in the JE database.
-   * @throws JebException If an error occurs in the JE backend.
    * @throws DirectoryException If an error occurs while verifying the backend.
    */
   public long verifyBackend(final RootContainer rootContainer, final Entry statEntry) throws StorageRuntimeException,
-      JebException, DirectoryException
+      DirectoryException
   {
-    Storage s;
     try
     {
-      return s.read(new ReadOperation<Long>()
+      return rootContainer.getStorage().read(new ReadOperation<Long>()
       {
         @Override
         public Long run(ReadableStorage txn) throws Exception
@@ -171,7 +168,7 @@
   }
 
   private long verifyBackend0(ReadableStorage txn, RootContainer rootContainer, Entry statEntry)
-      throws StorageRuntimeException, JebException, DirectoryException
+      throws StorageRuntimeException, DirectoryException
   {
     this.rootContainer = rootContainer;
     EntryContainer entryContainer =
@@ -224,7 +221,7 @@
             {
               LocalizableMessage msg = NOTE_JEB_SUBORDINATE_INDEXES_DISABLED
                   .get(rootContainer.getConfiguration().getBackendId());
-              throw new JebException(msg);
+              throw new StorageRuntimeException(msg.toString());
             }
           }
           else if ("id2subtree".equals(lowerName))
@@ -237,7 +234,7 @@
             {
               LocalizableMessage msg = NOTE_JEB_SUBORDINATE_INDEXES_DISABLED
                   .get(rootContainer.getConfiguration().getBackendId());
-              throw new JebException(msg);
+              throw new StorageRuntimeException(msg.toString());
             }
           }
           else if(lowerName.startsWith("vlv."))
@@ -245,7 +242,7 @@
             if(lowerName.length() < 5)
             {
               LocalizableMessage msg = ERR_JEB_VLV_INDEX_NOT_CONFIGURED.get(lowerName);
-              throw new JebException(msg);
+              throw new StorageRuntimeException(msg.toString());
             }
 
             VLVIndex vlvIndex =
@@ -254,35 +251,31 @@
             {
               LocalizableMessage msg =
                   ERR_JEB_VLV_INDEX_NOT_CONFIGURED.get(lowerName.substring(4));
-              throw new JebException(msg);
+              throw new StorageRuntimeException(msg.toString());
             }
 
             vlvIndexList.add(vlvIndex);
           }
           else
           {
-            AttributeType attrType =
-                DirectoryServer.getAttributeType(lowerName);
+            AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
             if (attrType == null)
             {
               LocalizableMessage msg = ERR_JEB_ATTRIBUTE_INDEX_NOT_CONFIGURED.get(index);
-              throw new JebException(msg);
+              throw new StorageRuntimeException(msg.toString());
             }
-            AttributeIndex attrIndex =
-                entryContainer.getAttributeIndex(attrType);
+            AttributeIndex attrIndex = entryContainer.getAttributeIndex(attrType);
             if (attrIndex == null)
             {
               LocalizableMessage msg = ERR_JEB_ATTRIBUTE_INDEX_NOT_CONFIGURED.get(index);
-              throw new JebException(msg);
+              throw new StorageRuntimeException(msg.toString());
             }
             attrIndexList.add(attrIndex);
           }
         }
       }
 
-      entryLimitMap =
-          new IdentityHashMap<Index,HashMap<ByteString,Long>>(
-              attrIndexList.size());
+      entryLimitMap = new IdentityHashMap<Index, HashMap<ByteString, Long>>(attrIndexList.size());
 
       // We will be updating these files independently of the indexes
       // so we need direct access to them rather than going through
@@ -421,7 +414,7 @@
     Cursor cursor = id2entry.openCursor(txn);
     try
     {
-      long storedEntryCount = id2entry.getRecordCount();
+      long storedEntryCount = id2entry.getRecordCount(txn);
       while (cursor.next())
       {
         ByteString key = cursor.getKey();
@@ -487,12 +480,10 @@
    * index cleanliness. For each ID in the index we check that the
    * entry it refers to does indeed contain the expected value.
    *
-   * @throws JebException If an error occurs in the JE backend.
    * @throws StorageRuntimeException If an error occurs in the JE database.
    * @throws DirectoryException If an error occurs reading values in the index.
    */
-  private void iterateIndex(ReadableStorage txn)
-      throws JebException, StorageRuntimeException, DirectoryException
+  private void iterateIndex(ReadableStorage txn) throws StorageRuntimeException, DirectoryException
   {
     if (verifyDN2ID)
     {
@@ -598,10 +589,9 @@
    * Iterate through the entries in ID2Children to perform a check for
    * index cleanliness.
    *
-   * @throws JebException If an error occurs in the JE backend.
    * @throws StorageRuntimeException If an error occurs in the JE database.
    */
-  private void iterateID2Children(ReadableStorage txn) throws JebException, StorageRuntimeException
+  private void iterateID2Children(ReadableStorage txn) throws StorageRuntimeException
   {
     Cursor cursor = id2c.openCursor(txn);
     try
@@ -727,10 +717,9 @@
    * Iterate through the entries in ID2Subtree to perform a check for
    * index cleanliness.
    *
-   * @throws JebException If an error occurs in the JE backend.
    * @throws StorageRuntimeException If an error occurs in the JE database.
    */
-  private void iterateID2Subtree(ReadableStorage txn) throws JebException, StorageRuntimeException
+  private void iterateID2Subtree(ReadableStorage txn) throws StorageRuntimeException
   {
     Cursor cursor = id2s.openCursor(txn);
     try
@@ -906,12 +895,11 @@
    *
    * @param vlvIndex The VLV index to perform the check against.
    * @param verifyID True to verify the IDs against id2entry.
-   * @throws JebException If an error occurs in the JE backend.
    * @throws StorageRuntimeException If an error occurs in the JE database.
    * @throws DirectoryException If an error occurs reading values in the index.
    */
   private void iterateVLVIndex(ReadableStorage txn, VLVIndex vlvIndex, boolean verifyID)
-      throws JebException, StorageRuntimeException, DirectoryException
+      throws StorageRuntimeException, DirectoryException
   {
     if(vlvIndex == null)
     {
@@ -1012,11 +1000,10 @@
    * Iterate through the entries in an attribute index to perform a check for
    * index cleanliness.
    * @param index The index database to be checked.
-   * @throws JebException If an error occurs in the JE backend.
    * @throws StorageRuntimeException If an error occurs in the JE database.
    */
   private void iterateAttrIndex(ReadableStorage txn, Index index, IndexingOptions options)
-      throws JebException, StorageRuntimeException
+      throws StorageRuntimeException
   {
     if (index == null)
     {
@@ -1487,16 +1474,6 @@
         }
         errorCount++;
       }
-      catch (JebException e)
-      {
-        if (logger.isTraceEnabled())
-        {
-          logger.traceException(e);
-          logger.trace("Error reading VLV index %s for entry %s: %s",
-              vlvIndex.getName(), entry.getName(), StaticUtils.getBacktrace(e));
-        }
-        errorCount++;
-      }
     }
   }
 

--
Gitblit v1.10.0