From 02bbeacbfb05101989dac510cbef7815fdf28a2e Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Mon, 01 Sep 2014 12:51:46 +0000
Subject: [PATCH] OPENDJ-1206 (CR-4393) Create a new ReplicationBackend/ChangelogBackend to support cn=changelog

---
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java                        |  237 +++----
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java                        |  109 --
 opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java                       |   44 
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java                |   64 +-
 opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java                               |    3 
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java                           |    2 
 opends/src/server/org/opends/server/backends/ChangelogBackend.java                                                       |  569 +++++++++++++------
 opends/src/server/org/opends/server/replication/server/ECLServerHandler.java                                             |   59 +
 opends/src/server/org/opends/server/backends/RootDSEBackend.java                                                         |    1 
 opends/src/server/org/opends/server/backends/SchemaBackend.java                                                          |    6 
 opends/src/guitools/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java                                 |   13 
 opends/src/server/org/opends/server/backends/BackupBackend.java                                                          |    3 
 opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java                            |   84 --
 opends/src/server/org/opends/server/api/Backend.java                                                                     |   65 +
 opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java                                   |    3 
 opends/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexer.java                             |   30 
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java                      |    2 
 opends/src/server/org/opends/server/backends/NullBackend.java                                                            |    1 
 opends/src/server/org/opends/server/core/PersistentSearch.java                                                           |   44 
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java                        |  121 +--
 opends/src/server/org/opends/server/backends/MonitorBackend.java                                                         |   21 
 opends/src/server/org/opends/server/replication/server/ReplicationServer.java                                            |  100 ---
 opends/src/server/org/opends/server/backends/jeb/BackendImpl.java                                                        |   14 
 opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java                            |   38 
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexerTest.java |   16 
 opends/src/server/org/opends/server/backends/TrustStoreBackend.java                                                      |   13 
 opends/src/server/org/opends/server/replication/server/ECLServerWriter.java                                              |    8 
 opends/src/server/org/opends/server/extensions/ConfigFileHandler.java                                                    |    8 
 opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java                                        |   42 -
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java                        |    2 
 opends/src/server/org/opends/server/backends/task/TaskBackend.java                                                       |    2 
 31 files changed, 844 insertions(+), 880 deletions(-)

diff --git a/opends/src/guitools/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java b/opends/src/guitools/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java
index ba8bfe9..30367ac 100644
--- a/opends/src/guitools/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java
+++ b/opends/src/guitools/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java
@@ -26,9 +26,6 @@
  */
 package org.opends.guitools.controlpanel.util;
 
-import static org.opends.messages.ConfigMessages.*;
-import static org.opends.server.util.StaticUtils.*;
-
 import java.io.File;
 import java.util.Collections;
 import java.util.HashMap;
@@ -63,6 +60,9 @@
 import org.opends.server.util.LDIFException;
 import org.opends.server.util.LDIFReader;
 
+import static org.opends.messages.ConfigMessages.*;
+import static org.opends.server.util.StaticUtils.*;
+
 /**
  * A class used to read the configuration from a file.  This config file
  * handler does not allow to modify the configuration, only to read it.
@@ -90,6 +90,7 @@
   @Override
   public void finalizeConfigHandler()
   {
+    finalizeBackend();
   }
 
   /** {@inheritDoc} */
@@ -299,12 +300,6 @@
 
   /** {@inheritDoc} */
   @Override
-  public void finalizeBackend()
-  {
-  }
-
-  /** {@inheritDoc} */
-  @Override
   public DN[] getBaseDNs()
   {
     return baseDNs;
diff --git a/opends/src/server/org/opends/server/api/Backend.java b/opends/src/server/org/opends/server/api/Backend.java
index fcbb39c..b35c1cd 100644
--- a/opends/src/server/org/opends/server/api/Backend.java
+++ b/opends/src/server/org/opends/server/api/Backend.java
@@ -29,12 +29,15 @@
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Queue;
 import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 import org.opends.messages.Message;
 import org.opends.server.admin.Configuration;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.*;
+import org.opends.server.core.PersistentSearch.CancellationCallback;
 import org.opends.server.monitors.BackendMonitor;
 import org.opends.server.types.*;
 
@@ -79,6 +82,10 @@
   /** The writability mode for this backend. */
   private WritabilityMode writabilityMode = WritabilityMode.ENABLED;
 
+  /** The set of persistent searches registered with this backend. */
+  private final ConcurrentLinkedQueue<PersistentSearch> persistentSearches =
+      new ConcurrentLinkedQueue<PersistentSearch>();
+
   /**
    * Configure this backend based on the information in the provided
    * configuration.
@@ -146,16 +153,26 @@
   /**
    * Performs any necessary work to finalize this backend, including
    * closing any underlying databases or connections and deregistering
-   * any suffixes that it manages with the Directory Server.  This may
+   * any suffixes that it manages with the Directory Server. This may
    * be called during the Directory Server shutdown process or if a
-   * backend is disabled with the server online.  It must not return
-   * until the backend is closed.
-   * <BR><BR>
-   * This method may not throw any exceptions.  If any problems are
-   * encountered, then they may be logged but the closure should
-   * progress as completely as possible.
+   * backend is disabled with the server online.
+   * It must not return until the backend is closed.
+   * <p>
+   * This method may not throw any exceptions. If any problems are encountered,
+   * then they may be logged but the closure should progress as completely as
+   * possible.
+   * <p>
+   * This method must be called by all overriding methods with
+   * <code>super.finalizeBackend()</code>.
    */
-  public abstract void finalizeBackend();
+  public void finalizeBackend()
+  {
+    for (PersistentSearch psearch : persistentSearches)
+    {
+      psearch.cancel();
+    }
+    persistentSearches.clear();
+  }
 
 
 
@@ -867,7 +884,39 @@
     return backendMonitor;
   }
 
+  /**
+   * Registers the provided persistent search operation with this backend so
+   * that it will be notified of any add, delete, modify, or modify DN
+   * operations that are performed.
+   *
+   * @param persistentSearch
+   *          The persistent search operation to register with this backend
+   */
+  public void registerPersistentSearch(PersistentSearch persistentSearch)
+  {
+    persistentSearches.add(persistentSearch);
 
+    persistentSearch.registerCancellationCallback(new CancellationCallback()
+    {
+      @Override
+      public void persistentSearchCancelled(PersistentSearch psearch)
+      {
+        persistentSearches.remove(psearch);
+      }
+    });
+  }
+
+  /**
+   * Returns the persistent searches currently active against this local
+   * backend.
+   *
+   * @return the list of persistent searches currently active against this local
+   *         backend
+   */
+  public Queue<PersistentSearch> getPersistentSearches()
+  {
+    return persistentSearches;
+  }
 
   /**
    * Sets the backend monitor for this backend.
diff --git a/opends/src/server/org/opends/server/backends/BackupBackend.java b/opends/src/server/org/opends/server/backends/BackupBackend.java
index 1c8e755..2fe9685 100644
--- a/opends/src/server/org/opends/server/backends/BackupBackend.java
+++ b/opends/src/server/org/opends/server/backends/BackupBackend.java
@@ -41,9 +41,9 @@
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.core.SearchOperation;
 import org.opends.server.loggers.debug.DebugTracer;
-import org.opends.server.types.*;
 import org.opends.server.schema.BooleanSyntax;
 import org.opends.server.schema.GeneralizedTimeSyntax;
+import org.opends.server.types.*;
 
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
@@ -207,6 +207,7 @@
   @Override
   public void finalizeBackend()
   {
+    super.finalizeBackend();
     currentConfig.removeBackupChangeListener(this);
 
     try
diff --git a/opends/src/server/org/opends/server/backends/ChangelogBackend.java b/opends/src/server/org/opends/server/backends/ChangelogBackend.java
index 0f0da2a..6808a0a 100644
--- a/opends/src/server/org/opends/server/backends/ChangelogBackend.java
+++ b/opends/src/server/org/opends/server/backends/ChangelogBackend.java
@@ -29,8 +29,7 @@
 import static org.opends.messages.ReplicationMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.loggers.ErrorLogger.*;
-import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
-import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.replication.protocol.StartECLSessionMsg.ECLRequestType.*;
 import static org.opends.server.replication.server.changelog.api.DBCursor.PositionStrategy.*;
 import static org.opends.server.util.LDIFWriter.*;
@@ -39,6 +38,7 @@
 
 import java.text.SimpleDateFormat;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.opends.messages.Category;
 import org.opends.messages.Message;
@@ -53,6 +53,7 @@
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.common.CSN;
 import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.replication.common.ServerState;
 import org.opends.server.replication.plugin.MultimasterReplication;
 import org.opends.server.replication.protocol.AddMsg;
 import org.opends.server.replication.protocol.DeleteMsg;
@@ -62,6 +63,7 @@
 import org.opends.server.replication.protocol.StartECLSessionMsg.ECLRequestType;
 import org.opends.server.replication.protocol.UpdateMsg;
 import org.opends.server.replication.server.ReplicationServer;
+import org.opends.server.replication.server.ReplicationServerDomain;
 import org.opends.server.replication.server.changelog.api.ChangeNumberIndexDB;
 import org.opends.server.replication.server.changelog.api.ChangeNumberIndexRecord;
 import org.opends.server.replication.server.changelog.api.ChangelogDB;
@@ -72,10 +74,11 @@
 import org.opends.server.replication.server.changelog.je.ECLMultiDomainDBCursor;
 import org.opends.server.replication.server.changelog.je.MultiDomainDBCursor;
 import org.opends.server.types.*;
+import org.opends.server.util.ServerConstants;
 import org.opends.server.util.StaticUtils;
 
 /**
- * A backend that provides access to the changelog, ie the "cn=changelog"
+ * A backend that provides access to the changelog, i.e. the "cn=changelog"
  * suffix. It is a read-only backend that is created by a
  * {@code ReplicationServer} and is not configurable.
  * <p>
@@ -85,8 +88,8 @@
  * request. The cookie provided in the control is used to retrieve entries from
  * the ReplicaDBs. The <code>changeNumber</code> attribute is not returned with
  * the entries.</li>
- * <li>Draft compat mode: when no "ECL Cookie Exchange Control" is provided with
- * the request. The entries are retrieved using the ChangeNumberIndexDB (or
+ * <li>Draft compatibility mode: when no "ECL Cookie Exchange Control" is provided
+ * with the request. The entries are retrieved using the ChangeNumberIndexDB (or
  * DraftDB, hence the name) and their attributes are set with the information
  * from the ReplicasDBs. The <code>changeNumber</code> attribute value is set
  * from the content of ChangeNumberIndexDB.</li>
@@ -134,8 +137,20 @@
   private static final AttributeType MODIFIERS_NAME_TYPE =
       DirectoryConfig.getAttributeType(OP_ATTR_MODIFIERS_NAME_LC, true);
 
-  /** The DN for the base changelog entry. */
-  private DN baseChangelogDN;
+  /** The base DN for the external change log. */
+  public static final DN CHANGELOG_BASE_DN;
+
+  static
+  {
+    try
+    {
+      CHANGELOG_BASE_DN = DN.decode(DN_EXTERNAL_CHANGELOG_ROOT);
+    }
+    catch (DirectoryException e)
+    {
+      throw new RuntimeException(e);
+    }
+  }
 
   /** The set of base DNs for this backend. */
   private DN[] baseDNs;
@@ -149,7 +164,7 @@
   private final ECLEnabledDomainPredicate domainPredicate;
 
   /**
-   * Creates a new backend with the provided repication server.
+   * Creates a new backend with the provided replication server.
    *
    * @param replicationServer
    *          The replication server on which the changes are read.
@@ -165,6 +180,23 @@
     setPrivateBackend(true);
   }
 
+  private ChangelogDB getChangelogDB()
+  {
+    return replicationServer.getChangelogDB();
+  }
+
+  /**
+   * Returns the ChangelogBackend configured for "cn=changelog" in this directory server.
+   *
+   * @return the ChangelogBackend configured for "cn=changelog" in this directory server
+   * @deprecated instead inject the required object where needed
+   */
+  @Deprecated
+  public static ChangelogBackend getInstance()
+  {
+    return (ChangelogBackend) DirectoryServer.getBackend(CHANGELOG_BASE_DN);
+  }
+
   /** {@inheritDoc} */
   @Override
   public void configureBackend(final Configuration config) throws ConfigException
@@ -176,29 +208,16 @@
   @Override
   public void initializeBackend() throws InitializationException
   {
-    try
-    {
-      baseChangelogDN = DN.decode(DN_EXTERNAL_CHANGELOG_ROOT);
-      baseDNs = new DN[] { baseChangelogDN };
-    }
-    catch (final DirectoryException e)
-    {
-      if (debugEnabled())
-      {
-        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-      }
-      throw new InitializationException(
-          ERR_BACKEND_CANNOT_DECODE_BACKEND_ROOT_DN.get(getBackendID(), getExceptionMessage(e)), e);
-    }
+    baseDNs = new DN[] { CHANGELOG_BASE_DN };
 
     try
     {
-      DirectoryServer.registerBaseDN(baseChangelogDN, this, true);
+      DirectoryServer.registerBaseDN(CHANGELOG_BASE_DN, this, true);
     }
     catch (final DirectoryException e)
     {
       throw new InitializationException(
-          ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(baseChangelogDN.toString(), getExceptionMessage(e)), e);
+          ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(CHANGELOG_BASE_DN.toString(), getExceptionMessage(e)), e);
     }
   }
 
@@ -206,9 +225,11 @@
   @Override
   public void finalizeBackend()
   {
+    super.finalizeBackend();
+
     try
     {
-      DirectoryServer.deregisterBaseDN(baseChangelogDN);
+      DirectoryServer.deregisterBaseDN(CHANGELOG_BASE_DN);
     }
     catch (final DirectoryException e)
     {
@@ -299,7 +320,7 @@
     @Override
     public DN getBaseDN()
     {
-      return baseChangelogDN;
+      return CHANGELOG_BASE_DN;
     }
 
     @Override
@@ -313,6 +334,13 @@
     {
       return SearchScope.WHOLE_SUBTREE;
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public Object setAttachment(String name, Object value)
+    {
+      return null;
+    }
   }
 
   /** {@inheritDoc} */
@@ -320,7 +348,7 @@
   public long numSubordinates(final DN entryDN, final boolean subtree) throws DirectoryException
   {
     // Compute the num subordinates only for the base DN
-    if (entryDN == null || !baseChangelogDN.equals(entryDN))
+    if (entryDN == null || !CHANGELOG_BASE_DN.equals(entryDN))
     {
       return -1;
     }
@@ -329,11 +357,9 @@
       return 1;
     }
     // Search with cookie mode to count all update messages
-    final Set<String> excludedDomains = MultimasterReplication.getECLDisabledDomains();
-    excludedDomains.add(DN_EXTERNAL_CHANGELOG_ROOT);
-    SearchParams params = new SearchParams("0", excludedDomains);
+    final SearchParams params = new SearchParams(getExcludedDomains());
     params.requestType = REQUEST_TYPE_FROM_COOKIE;
-    params.multiDomainServerState = new MultiDomainServerState();
+    params.cookie = new MultiDomainServerState();
     NumSubordinatesSearchOperation searchOp = new NumSubordinatesSearchOperation();
     try
     {
@@ -342,11 +368,118 @@
     catch (ChangelogException e)
     {
       throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CHANGELOG_BACKEND_NUM_SUBORDINATES.get(
-          baseChangelogDN.toString(), stackTraceToSingleLineString(e)));
+          CHANGELOG_BASE_DN.toString(), stackTraceToSingleLineString(e)));
     }
     return searchOp.numSubordinates;
   }
 
+  private Set<String> getExcludedDomains()
+  {
+    final Set<String> domains = MultimasterReplication.getECLDisabledDomains();
+    domains.add(DN_EXTERNAL_CHANGELOG_ROOT);
+    return domains;
+  }
+
+  /**
+   * Notifies persistent searches of this backend that a new entry was added to it.
+   * <p>
+   * Note: This method is called in a multi-threaded context.
+   *
+   * @param baseDN
+   *          the baseDN of the newly added entry.
+   * @param changeNumber
+   *          the change number of the newly added entry. It will be greater
+   *          than zero for entries added to the change number index and less
+   *          than or equal to zero for entries added to any replica DB
+   * @param cookieString
+   *          a string representing the cookie of the newly added entry.
+   *          This is only meaningful for entries added to the change number index
+   * @param updateMsg
+   *          the update message of the newly added entry
+   * @throws ChangelogException
+   *           If a problem occurs while notifying of the newly added entry.
+   */
+  public void notifyEntryAdded(DN baseDN, long changeNumber, String cookieString, UpdateMsg updateMsg)
+      throws ChangelogException
+  {
+    final boolean isCookieEntry = changeNumber <= 0;
+    final List<SearchOperation> pSearchOps = getPersistentSearches(isCookieEntry);
+    if (pSearchOps.isEmpty() || !(updateMsg instanceof LDAPUpdateMsg))
+    {
+      return;
+    }
+
+    try
+    {
+      final Entry entry = createEntryFromMsg(baseDN, changeNumber, cookieString, updateMsg);
+      for (SearchOperation pSearchOp : pSearchOps)
+      {
+        final EntrySender entrySender = (EntrySender)
+            pSearchOp.getAttachment(OID_ECL_COOKIE_EXCHANGE_CONTROL);
+
+        // when returning changesOnly, the first incoming update must return
+        // the base entry before any other changes,
+        // so force sending now, when protected by the synchronized block
+        if (isCookieEntry)
+        { // cookie based search
+          final String cookieStr;
+          synchronized (entrySender)
+          { // forbid concurrent updates to the cookie
+            entrySender.cookie.update(baseDN, updateMsg.getCSN());
+            cookieStr = entrySender.cookie.toString();
+
+            entrySender.sendBaseChangelogEntry(true);
+          }
+          final Entry entry2 = createEntryFromMsg(baseDN, changeNumber, cookieStr, updateMsg);
+          // FIXME JNR use this instead of previous line:
+          // entry.replaceAttribute(Attributes.create("changelogcookie", cookieStr));
+          entrySender.sendEntryIfMatches(entry2, cookieStr);
+        }
+        else
+        { // draft changeNumber search
+          if (!entrySender.hasReturnedBaseEntry.get())
+          {
+            synchronized (entrySender)
+            {
+              entrySender.sendBaseChangelogEntry(true);
+            }
+          }
+          entrySender.sendEntryIfMatches(entry, null);
+        }
+      }
+    }
+    catch (DirectoryException e)
+    {
+      throw new ChangelogException(e.getMessageObject(), e);
+    }
+  }
+
+  private List<SearchOperation> getPersistentSearches(boolean wantCookieBasedSearch)
+  {
+    final List<SearchOperation> results = new ArrayList<SearchOperation>();
+    for (PersistentSearch pSearch : getPersistentSearches())
+    {
+      final SearchOperation op = pSearch.getSearchOperation();
+      if (wantCookieBasedSearch == isCookieBased(op))
+      {
+        results.add(op);
+      }
+    }
+    return results;
+  }
+
+  private boolean isCookieBased(final SearchOperation searchOp)
+  {
+    for (Control c : searchOp.getRequestControls())
+    {
+      if (OID_ECL_COOKIE_EXCHANGE_CONTROL.equals(c.getOID()))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /** {@inheritDoc} */
   @Override
   public void addEntry(Entry entry, AddOperation addOperation)
@@ -409,9 +542,7 @@
 
   private SearchParams buildSearchParameters(final SearchOperation searchOperation) throws DirectoryException
   {
-    final Set<String> excludedDomains = MultimasterReplication.getECLDisabledDomains();
-    excludedDomains.add(DN_EXTERNAL_CHANGELOG_ROOT);
-    final SearchParams params = new SearchParams(searchOperation.toString(), excludedDomains);
+    final SearchParams params = new SearchParams(getExcludedDomains());
     final ExternalChangelogRequestControl eclRequestControl =
         searchOperation.getRequestControl(ExternalChangelogRequestControl.DECODER);
     if (eclRequestControl == null)
@@ -421,7 +552,7 @@
     else
     {
       params.requestType = REQUEST_TYPE_FROM_COOKIE;
-      params.multiDomainServerState = eclRequestControl.getCookie();
+      params.cookie = eclRequestControl.getCookie();
     }
     return params;
   }
@@ -523,7 +654,7 @@
   {
     try
     {
-      return numSubordinates(baseChangelogDN, true) + 1;
+      return numSubordinates(CHANGELOG_BASE_DN, true) + 1;
     }
     catch (DirectoryException e)
     {
@@ -543,33 +674,28 @@
   static class SearchParams
   {
     private ECLRequestType requestType;
-    private final String operationId;
     private final Set<String> excludedBaseDNs;
     private long lowestChangeNumber = -1;
     private long highestChangeNumber = -1;
     private CSN csn = new CSN(0, 0, 0);
-    private MultiDomainServerState multiDomainServerState;
+    private MultiDomainServerState cookie;
 
     /**
      * Creates search parameters.
      */
     SearchParams()
     {
-      operationId = "";
-      excludedBaseDNs = Collections.emptySet();
+      this.excludedBaseDNs = Collections.emptySet();
     }
 
     /**
      * Creates search parameters with provided id and excluded domain DNs.
      *
-     * @param operationId
-     *          The id of the operation.
      * @param excludedBaseDNs
      *          Set of DNs to exclude from search.
      */
-    SearchParams(final String operationId, final Set<String> excludedBaseDNs)
+    SearchParams(final Set<String> excludedBaseDNs)
     {
-      this.operationId = operationId;
       this.excludedBaseDNs = excludedBaseDNs;
     }
 
@@ -802,45 +928,40 @@
   private void searchFromCookie(final SearchParams searchParams, final SearchOperation searchOperation)
       throws DirectoryException, ChangelogException
   {
-    final ReplicationDomainDB replicationDomainDB = replicationServer.getChangelogDB().getReplicationDomainDB();
     validateProvidedCookie(searchParams);
+    final boolean isPersistentSearch = isPersistentSearch(searchOperation);
 
-    boolean hasReturnedBaseEntry = false;
+    final EntrySender entrySender = new EntrySender(searchOperation, searchParams.cookie);
+    if (isPersistentSearch)
+    {
+      searchOperation.setAttachment(OID_ECL_COOKIE_EXCHANGE_CONTROL, entrySender);
+    }
+
     ECLMultiDomainDBCursor replicaUpdatesCursor = null;
     try
     {
+      final ReplicationDomainDB replicationDomainDB = getChangelogDB().getReplicationDomainDB();
       final MultiDomainDBCursor cursor = replicationDomainDB.getCursorFrom(
-          searchParams.multiDomainServerState, AFTER_MATCHING_KEY, searchParams.getExcludedBaseDNs());
+          searchParams.cookie, AFTER_MATCHING_KEY, searchParams.getExcludedBaseDNs());
       replicaUpdatesCursor = new ECLMultiDomainDBCursor(domainPredicate, cursor);
 
-      MultiDomainServerState cookie = searchParams.multiDomainServerState;
       boolean continueSearch = true;
       while (continueSearch && replicaUpdatesCursor.next())
       {
-        // Handle creation of base changelog entry on first update message found
-        if (!hasReturnedBaseEntry)
-        {
-          if (!returnBaseChangelogEntry(searchOperation, true))
-          {
-            return;
-          }
-          hasReturnedBaseEntry = true;
-        }
         // Handle the update message
         final UpdateMsg updateMsg = replicaUpdatesCursor.getRecord();
         final DN domainBaseDN = replicaUpdatesCursor.getData();
-        cookie.update(domainBaseDN, updateMsg.getCSN());
-        final Entry entry = createEntryFromMsg(domainBaseDN, 0L, cookie.toString(), updateMsg);
-        if (matchBaseAndScopeAndFilter(entry, searchOperation))
-        {
-          Control control = new EntryChangelogNotificationControl(true, cookie.toString());
-          continueSearch = searchOperation.returnEntry(entry, Arrays.asList(control));
-        }
+        searchParams.cookie.update(domainBaseDN, updateMsg.getCSN());
+        final String cookieString = searchParams.cookie.toString();
+
+        final Entry entry = createEntryFromMsg(domainBaseDN, 0, cookieString, updateMsg);
+        continueSearch = entrySender.sendEntryIfMatches(entry, cookieString);
       }
-      // Handle creation of base changelog entry when no update message is found
-      if (!hasReturnedBaseEntry)
+
+      if (!isPersistentSearch)
       {
-        returnBaseChangelogEntry(searchOperation, false);
+        // send the base changelog entry if no update message is found
+        entrySender.sendBaseChangelogEntry(false);
       }
     }
     finally
@@ -849,6 +970,52 @@
     }
   }
 
+  private boolean isPersistentSearch(SearchOperation op)
+  {
+    for (PersistentSearch pSearch : getPersistentSearches())
+    {
+      if (op == pSearch.getSearchOperation())
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void registerPersistentSearch(PersistentSearch pSearch)
+  {
+    super.registerPersistentSearch(pSearch);
+
+    final SearchOperation searchOp = pSearch.getSearchOperation();
+    if (pSearch.isChangesOnly())
+    {
+      // this persistent search will not go through #search0() down below
+      // so we must initialize the cookie here
+      searchOp.setAttachment(OID_ECL_COOKIE_EXCHANGE_CONTROL,
+          new EntrySender(searchOp, getNewestCookie(searchOp)));
+    }
+  }
+
+  private MultiDomainServerState getNewestCookie(SearchOperation searchOp)
+  {
+    if (!isCookieBased(searchOp))
+    {
+      return null;
+    }
+
+    final MultiDomainServerState cookie = new MultiDomainServerState();
+    for (final Iterator<ReplicationServerDomain> it =
+        replicationServer.getDomainIterator(); it.hasNext();)
+    {
+      final DN baseDN = it.next().getBaseDN();
+      final ServerState state = getChangelogDB().getReplicationDomainDB().getDomainNewestCSNs(baseDN);
+      cookie.update(baseDN, state);
+    }
+    return cookie;
+  }
+
   /**
    * Validates the cookie contained in search parameters by checking its content
    * with the actual replication server state.
@@ -858,7 +1025,7 @@
    */
   private void validateProvidedCookie(final SearchParams searchParams) throws DirectoryException
   {
-    final MultiDomainServerState state = searchParams.multiDomainServerState;
+    final MultiDomainServerState state = searchParams.cookie;
     if (state != null && !state.isEmpty())
     {
       replicationServer.validateServerState(state, searchParams.getExcludedBaseDNs());
@@ -871,102 +1038,67 @@
   private void searchFromChangeNumber(final SearchParams params, final SearchOperation searchOperation)
       throws ChangelogException, DirectoryException
   {
-    boolean hasReturnedBaseEntry = false;
-    final ChangelogDB changelogDB = replicationServer.getChangelogDB();
+    final EntrySender entrySender = new EntrySender(searchOperation, null);
+    final boolean isPersistentSearch = isPersistentSearch(searchOperation);
+    if (isPersistentSearch)
+    {
+      searchOperation.setAttachment(OID_ECL_COOKIE_EXCHANGE_CONTROL, entrySender);
+    }
+
     DBCursor<ChangeNumberIndexRecord> cnIndexDBCursor = null;
     MultiDomainDBCursor replicaUpdatesCursor = null;
-    try {
-      cnIndexDBCursor = getCNIndexDBCursor(changelogDB, params.lowestChangeNumber);
+    try
+    {
+      cnIndexDBCursor = getCNIndexDBCursor(params.lowestChangeNumber);
       boolean continueSearch = true;
       while (continueSearch && cnIndexDBCursor.next())
       {
-        // Handle creation of base changelog entry on cnIndex record found
-        if (!hasReturnedBaseEntry)
-        {
-          if (!returnBaseChangelogEntry(searchOperation, true))
-          {
-            return;
-          }
-          hasReturnedBaseEntry = true;
-        }
         // Handle the current cnIndex record
         final ChangeNumberIndexRecord cnIndexRecord = cnIndexDBCursor.getRecord();
         if (replicaUpdatesCursor == null)
         {
-          replicaUpdatesCursor = initializeReplicaUpdatesCursor(changelogDB, cnIndexRecord);
+          replicaUpdatesCursor = initializeReplicaUpdatesCursor(cnIndexRecord);
         }
         continueSearch = params.changeNumberIsInRange(cnIndexRecord.getChangeNumber());
         if (continueSearch)
         {
-           UpdateMsg updateMsg = findReplicaUpdateMessage(cnIndexRecord, replicaUpdatesCursor);
-           if (updateMsg != null)
-           {
-             continueSearch = returnEntryForUpdateMessage(searchOperation, cnIndexRecord, updateMsg);
-             replicaUpdatesCursor.next();
-           }
+          UpdateMsg updateMsg = findReplicaUpdateMessage(cnIndexRecord, replicaUpdatesCursor);
+          if (updateMsg != null)
+          {
+            continueSearch = sendEntryForUpdateMessage(entrySender, cnIndexRecord, updateMsg);
+            replicaUpdatesCursor.next();
+          }
         }
       }
-      // Handle creation of base changelog entry when no update message is found
-      if (!hasReturnedBaseEntry)
+
+      if (!isPersistentSearch)
       {
-        returnBaseChangelogEntry(searchOperation, false);
+        // send the base changelog entry if no update message is found
+        entrySender.sendBaseChangelogEntry(false);
       }
     }
-    finally {
+    finally
+    {
       StaticUtils.close(cnIndexDBCursor, replicaUpdatesCursor);
     }
   }
 
   /**
-   * Create and returns the base changelog entry to provided search operation.
-   *
    * @return {@code true} if search should continue, {@code false} otherwise
    */
-  private boolean returnBaseChangelogEntry(final SearchOperation searchOperation, boolean hasSubordinates)
-      throws DirectoryException
+  private boolean sendEntryForUpdateMessage(EntrySender entrySender,
+      ChangeNumberIndexRecord cnIndexRecord, UpdateMsg updateMsg) throws DirectoryException
   {
-    final DN baseDN = searchOperation.getBaseDN();
-    final SearchFilter filter = searchOperation.getFilter();
-    final SearchScope scope = searchOperation.getScope();
-
-    if (baseChangelogDN.matchesBaseAndScope(baseDN, scope))
-    {
-      final Entry entry = buildBaseChangelogEntry(hasSubordinates);
-      if (filter.matchesEntry(entry) && !searchOperation.returnEntry(entry, null))
-      {
-        // Abandon, size limit reached.
-        return false;
-      }
-    }
-    if (baseDN.equals(baseChangelogDN) && scope.equals(SearchScope.BASE_OBJECT))
-    {
-      // Only the change log root entry was requested
-      return false;
-    }
-    return true;
-  }
-
-  /**
-   * @return {@code true} if search should continue, {@code false} otherwise
-   */
-  private boolean returnEntryForUpdateMessage(
-      final SearchOperation searchOperation,
-      final ChangeNumberIndexRecord cnIndexRecord,
-      final UpdateMsg updateMsg)
-          throws DirectoryException
-  {
+    final DN baseDN = cnIndexRecord.getBaseDN();
     final MultiDomainServerState cookie = new MultiDomainServerState(cnIndexRecord.getPreviousCookie());
-    final DN changeDN = cnIndexRecord.getBaseDN();
-    cookie.update(changeDN, cnIndexRecord.getCSN());
-    final Entry entry = createEntryFromMsg(changeDN, cnIndexRecord.getChangeNumber(), cookie.toString(), updateMsg);
-    if (matchBaseAndScopeAndFilter(entry, searchOperation))
-    {
-      return searchOperation.returnEntry(entry, null);
-    }
-    return true;
+    cookie.update(baseDN, cnIndexRecord.getCSN());
+    final String cookieString = cookie.toString();
+
+    final Entry entry = createEntryFromMsg(baseDN, cnIndexRecord.getChangeNumber(), cookieString, updateMsg);
+    return entrySender.sendEntryIfMatches(entry, null);
   }
 
-  private MultiDomainDBCursor initializeReplicaUpdatesCursor(final ChangelogDB changelogDB,
+  private MultiDomainDBCursor initializeReplicaUpdatesCursor(
       final ChangeNumberIndexRecord cnIndexRecord) throws ChangelogException
   {
     final MultiDomainServerState state = new MultiDomainServerState();
@@ -975,7 +1107,7 @@
     // No need for ECLMultiDomainDBCursor in this case
     // as updateMsg will be matched with cnIndexRecord
     final MultiDomainDBCursor replicaUpdatesCursor =
-        changelogDB.getReplicationDomainDB().getCursorFrom(state, ON_MATCHING_KEY);
+        getChangelogDB().getReplicationDomainDB().getCursorFrom(state, ON_MATCHING_KEY);
     replicaUpdatesCursor.next();
     return replicaUpdatesCursor;
   }
@@ -1023,10 +1155,10 @@
   }
 
   /** Returns a cursor on CNIndexDB for the provided first change number. */
-  private DBCursor<ChangeNumberIndexRecord> getCNIndexDBCursor(final ChangelogDB changelogDB,
+  private DBCursor<ChangeNumberIndexRecord> getCNIndexDBCursor(
       final long firstChangeNumber) throws ChangelogException
   {
-    final ChangeNumberIndexDB cnIndexDB = changelogDB.getChangeNumberIndexDB();
+    final ChangeNumberIndexDB cnIndexDB = getChangelogDB().getChangeNumberIndexDB();
     long changeNumberToUse = firstChangeNumber;
     if (changeNumberToUse <= 1)
     {
@@ -1036,31 +1168,6 @@
     return cnIndexDB.getCursorFrom(changeNumberToUse);
   }
 
-  /** Indicates if the provided entry matches the filter, base and scope. */
-  private boolean matchBaseAndScopeAndFilter(Entry entry, SearchOperation searchOp) throws DirectoryException
-  {
-    return entry.matchesBaseAndScope(searchOp.getBaseDN(), searchOp.getScope())
-        && searchOp.getFilter().matchesEntry(entry);
-  }
-
-  /**
-   * Retrieves the base changelog entry.
-   */
-  private Entry buildBaseChangelogEntry(boolean hasSubordinates)
-  {
-    final Map<AttributeType, List<Attribute>> userAttrs = new LinkedHashMap<AttributeType,List<Attribute>>();
-    final Map<AttributeType, List<Attribute>> operationalAttrs = new LinkedHashMap<AttributeType,List<Attribute>>();
-
-    addAttributeByUppercaseName(ATTR_COMMON_NAME, ATTR_COMMON_NAME, BACKEND_ID, userAttrs, operationalAttrs);
-    addAttributeByUppercaseName(ATTR_SUBSCHEMA_SUBENTRY_LC, ATTR_SUBSCHEMA_SUBENTRY,
-        ConfigConstants.DN_DEFAULT_SCHEMA_ROOT, userAttrs, operationalAttrs);
-    addAttributeByUppercaseName("hassubordinates", "hasSubordinates", Boolean.toString(hasSubordinates),
-        userAttrs, operationalAttrs);
-    addAttributeByUppercaseName("entrydn", "entryDN", baseChangelogDN.toString(),
-        userAttrs, operationalAttrs);
-    return new Entry(baseChangelogDN, CHANGELOG_ROOT_OBJECT_CLASSES, userAttrs, operationalAttrs);
-  }
-
   /**
    * Creates a changelog entry.
    */
@@ -1225,16 +1332,16 @@
   {
     final CSN csn = msg.getCSN();
     String dnString;
-    if (changeNumber == 0)
-    {
-      // Cookie mode
-      dnString = "replicationCSN=" + csn + "," + baseDN.toString() + "," + DN_EXTERNAL_CHANGELOG_ROOT;
-    }
-    else
+    if (changeNumber > 0)
     {
       // Draft compat mode
       dnString = "changeNumber=" + changeNumber + "," + DN_EXTERNAL_CHANGELOG_ROOT;
     }
+    else
+    {
+      // Cookie mode
+      dnString = "replicationCSN=" + csn + "," + baseDN + "," + DN_EXTERNAL_CHANGELOG_ROOT;
+    }
 
     final Map<AttributeType, List<Attribute>> userAttrs = new LinkedHashMap<AttributeType, List<Attribute>>();
     final Map<AttributeType, List<Attribute>> opAttrs = new LinkedHashMap<AttributeType, List<Attribute>>();
@@ -1247,7 +1354,7 @@
     addAttributeByType("entrydn", "entryDN", dnString, userAttrs, opAttrs);
 
     // REQUIRED attributes
-    if (changeNumber != 0)
+    if (changeNumber > 0)
     {
       addAttributeByType("changenumber", "changeNumber", String.valueOf(changeNumber), userAttrs, opAttrs);
     }
@@ -1277,7 +1384,8 @@
     {
       addAttributeByType("targetentryuuid", "targetEntryUUID", targetUUID, userAttrs, opAttrs);
     }
-    addAttributeByType("changelogcookie", "changeLogCookie", cookie, userAttrs, opAttrs);
+    final String cookie2 = cookie != null ? cookie : "";
+    addAttributeByType("changelogcookie", "changeLogCookie", cookie2, userAttrs, opAttrs);
 
     final List<RawAttribute> includedAttributes = msg.getEclIncludes();
     if (includedAttributes != null && !includedAttributes.isEmpty())
@@ -1300,6 +1408,116 @@
     return new Entry(DN.decode(dnString), CHANGELOG_ENTRY_OBJECT_CLASSES, userAttrs, opAttrs);
   }
 
+  /**
+   * Used to send entries to searches on cn=changelog. This class ensures the
+   * base changelog entry is sent before sending any other entry. It is also
+   * used as a store when going from the "initial search" phase to the
+   * "persistent search" phase.
+   */
+  private static class EntrySender
+  {
+
+    private final SearchOperation searchOp;
+    /**
+     * Used by the cookie-based searches to communicate the cookie between the
+     * initial search phase and the persistent search phase. This is unused with
+     * draft change number searches.
+     */
+    private final MultiDomainServerState cookie;
+    private final AtomicBoolean hasReturnedBaseEntry = new AtomicBoolean();
+
+    public EntrySender(SearchOperation searchOp, MultiDomainServerState cookie)
+    {
+      this.searchOp = searchOp;
+      this.cookie = cookie;
+    }
+
+    /**
+     * Sends the entry if it matches the base, scope and filter of the current search operation.
+     * It will also send the base changelog entry if it needs to be sent and was not sent before.
+     *
+     * @return {@code true} if search should continue, {@code false} otherwise
+     */
+    private boolean sendEntryIfMatches(Entry entry, String cookie) throws DirectoryException
+    {
+      // About to send one entry: ensure the base changelog entry is sent first
+      if (!sendBaseChangelogEntry(true))
+      {
+        // only return the base entry: stop here
+        return false;
+      }
+      if (matchBaseAndScopeAndFilter(entry))
+      {
+        return searchOp.returnEntry(entry, getControls(cookie));
+      }
+      // maybe the next entry will match?
+      return true;
+    }
+
+    /** Indicates if the provided entry matches the filter, base and scope. */
+    private boolean matchBaseAndScopeAndFilter(Entry entry) throws DirectoryException
+    {
+      return entry.matchesBaseAndScope(searchOp.getBaseDN(), searchOp.getScope())
+          && searchOp.getFilter().matchesEntry(entry);
+    }
+
+    private List<Control> getControls(String cookie)
+    {
+      if (cookie != null)
+      {
+        Control c = new EntryChangelogNotificationControl(true, cookie);
+        return Arrays.asList(c);
+      }
+      return Collections.emptyList();
+    }
+
+    /**
+     * Create and returns the base changelog entry to the underlying search operation.
+     *
+     * @return {@code true} if search should continue, {@code false} otherwise
+     */
+    private boolean sendBaseChangelogEntry(boolean hasSubordinates) throws DirectoryException
+    {
+      if (hasReturnedBaseEntry.compareAndSet(false, true))
+      {
+        final DN baseDN = searchOp.getBaseDN();
+        final SearchFilter filter = searchOp.getFilter();
+        final SearchScope scope = searchOp.getScope();
+
+        if (ChangelogBackend.CHANGELOG_BASE_DN.matchesBaseAndScope(baseDN, scope))
+        {
+          final Entry entry = buildBaseChangelogEntry(hasSubordinates);
+          if (filter.matchesEntry(entry) && !searchOp.returnEntry(entry, null))
+          {
+            // Abandon, size limit reached.
+            return false;
+          }
+        }
+        return !baseDN.equals(ChangelogBackend.CHANGELOG_BASE_DN)
+            || !scope.equals(SearchScope.BASE_OBJECT);
+      }
+      return true;
+    }
+
+    private Entry buildBaseChangelogEntry(boolean hasSubordinates)
+    {
+      final Map<AttributeType, List<Attribute>> userAttrs =
+          new LinkedHashMap<AttributeType, List<Attribute>>();
+      final Map<AttributeType, List<Attribute>> operationalAttrs =
+          new LinkedHashMap<AttributeType, List<Attribute>>();
+
+      addAttributeByUppercaseName(ATTR_COMMON_NAME, ATTR_COMMON_NAME,
+          ChangelogBackend.BACKEND_ID, userAttrs, operationalAttrs);
+      addAttributeByUppercaseName(ATTR_SUBSCHEMA_SUBENTRY_LC, ATTR_SUBSCHEMA_SUBENTRY,
+          ConfigConstants.DN_DEFAULT_SCHEMA_ROOT, userAttrs, operationalAttrs);
+      addAttributeByUppercaseName("hassubordinates", "hasSubordinates",
+          Boolean.toString(hasSubordinates), userAttrs, operationalAttrs);
+      addAttributeByUppercaseName("entrydn", "entryDN",
+          ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT, userAttrs, operationalAttrs);
+      return new Entry(CHANGELOG_BASE_DN, CHANGELOG_ROOT_OBJECT_CLASSES, userAttrs, operationalAttrs);
+    }
+  }
+
   private static void addAttribute(final Entry e, final String attrType, final String attrValue)
   {
     e.addAttribute(Attributes.create(attrType, attrValue), null);
@@ -1313,7 +1531,7 @@
     addAttribute(attrNameLowercase, attrNameUppercase, attrValue, userAttrs, operationalAttrs, true);
   }
 
-  private void addAttributeByUppercaseName(String attrNameLowercase,
+  private static void addAttributeByUppercaseName(String attrNameLowercase,
       String attrNameUppercase,  String attrValue,
       Map<AttributeType, List<Attribute>> userAttrs,
       Map<AttributeType, List<Attribute>> operationalAttrs)
@@ -1331,8 +1549,9 @@
     {
       attrType = DirectoryServer.getDefaultAttributeType(attrNameUppercase);
     }
-    final Attribute a = addByType ?
-        Attributes.create(attrType, attrValue) : Attributes.create(attrNameUppercase, attrValue);
+    final Attribute a = addByType
+        ? Attributes.create(attrType, attrValue)
+        : Attributes.create(attrNameUppercase, attrValue);
     final List<Attribute> attrList = Collections.singletonList(a);
     if (attrType.isOperational())
     {
diff --git a/opends/src/server/org/opends/server/backends/MonitorBackend.java b/opends/src/server/org/opends/server/backends/MonitorBackend.java
index 5223c94..eb14872 100644
--- a/opends/src/server/org/opends/server/backends/MonitorBackend.java
+++ b/opends/src/server/org/opends/server/backends/MonitorBackend.java
@@ -26,14 +26,6 @@
  */
 package org.opends.server.backends;
 
-import static org.opends.messages.BackendMessages.*;
-import static org.opends.messages.ConfigMessages.*;
-import static org.opends.server.config.ConfigConstants.*;
-import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
-import static org.opends.server.loggers.debug.DebugLogger.getTracer;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
 import java.util.*;
 
 import org.opends.messages.Message;
@@ -51,6 +43,13 @@
 import org.opends.server.util.TimeThread;
 import org.opends.server.util.Validator;
 
+import static org.opends.messages.BackendMessages.*;
+import static org.opends.messages.ConfigMessages.*;
+import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
 /**
  * This class defines a backend to hold Directory Server monitor entries. It
  * will not actually store anything, but upon request will retrieve the
@@ -65,9 +64,10 @@
    */
   private static final DebugTracer TRACER = getTracer();
 
-  /** The set of user-defined attributes that will be included in the base
+  /**
+   * The set of user-defined attributes that will be included in the base
    * monitor entry.
-*/
+   */
   private ArrayList<Attribute> userDefinedAttributes;
 
   /** The set of objectclasses that will be used in monitor entries. */
@@ -349,6 +349,7 @@
   @Override
   public void finalizeBackend()
   {
+    super.finalizeBackend();
     currentConfig.removeMonitorChangeListener(this);
     try
     {
diff --git a/opends/src/server/org/opends/server/backends/NullBackend.java b/opends/src/server/org/opends/server/backends/NullBackend.java
index 032e758..01cf98a 100644
--- a/opends/src/server/org/opends/server/backends/NullBackend.java
+++ b/opends/src/server/org/opends/server/backends/NullBackend.java
@@ -236,6 +236,7 @@
   @Override
   public synchronized void finalizeBackend()
   {
+    super.finalizeBackend();
     for (DN dn : baseDNs)
     {
       try
diff --git a/opends/src/server/org/opends/server/backends/RootDSEBackend.java b/opends/src/server/org/opends/server/backends/RootDSEBackend.java
index 77a99f1..5eb7c6e 100644
--- a/opends/src/server/org/opends/server/backends/RootDSEBackend.java
+++ b/opends/src/server/org/opends/server/backends/RootDSEBackend.java
@@ -290,6 +290,7 @@
   @Override
   public void finalizeBackend()
   {
+    super.finalizeBackend();
     currentConfig.removeChangeListener(this);
   }
 
diff --git a/opends/src/server/org/opends/server/backends/SchemaBackend.java b/opends/src/server/org/opends/server/backends/SchemaBackend.java
index 29643d3..368a49c 100644
--- a/opends/src/server/org/opends/server/backends/SchemaBackend.java
+++ b/opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -51,11 +51,12 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
+
 import javax.crypto.Mac;
 
 import org.opends.messages.Message;
-import org.opends.server.admin.std.server.SchemaBackendCfg;
 import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.server.SchemaBackendCfg;
 import org.opends.server.api.AlertGenerator;
 import org.opends.server.api.Backend;
 import org.opends.server.api.ClientConnection;
@@ -89,8 +90,8 @@
 import static org.opends.messages.ConfigMessages.*;
 import static org.opends.messages.SchemaMessages.*;
 import static org.opends.server.config.ConfigConstants.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.loggers.ErrorLogger.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.schema.SchemaConstants.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
@@ -501,6 +502,7 @@
   @Override
   public void finalizeBackend()
   {
+    super.finalizeBackend();
     currentConfig.removeSchemaChangeListener(this);
 
     for (DN baseDN : baseDNs)
diff --git a/opends/src/server/org/opends/server/backends/TrustStoreBackend.java b/opends/src/server/org/opends/server/backends/TrustStoreBackend.java
index ae4b1f7..30e4a1c 100644
--- a/opends/src/server/org/opends/server/backends/TrustStoreBackend.java
+++ b/opends/src/server/org/opends/server/backends/TrustStoreBackend.java
@@ -26,12 +26,6 @@
  */
 package org.opends.server.backends;
 
-import static org.opends.messages.BackendMessages.*;
-import static org.opends.server.config.ConfigConstants.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
@@ -79,6 +73,12 @@
 import org.opends.server.util.SetupUtils;
 import org.opends.server.util.Validator;
 
+import static org.opends.messages.BackendMessages.*;
+import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
 /**
  * This class defines a backend used to provide an LDAP view of public keys
  * stored in a key store.
@@ -367,6 +367,7 @@
   @Override
   public void finalizeBackend()
   {
+    super.finalizeBackend();
     configuration.addTrustStoreChangeListener(this);
 
     try
diff --git a/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java b/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
index fb2f8a9..e86a7a0 100644
--- a/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
+++ b/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -332,7 +332,7 @@
   @Override
   public void finalizeBackend()
   {
-    // Deregister as a change listener.
+    super.finalizeBackend();
     cfg.removeLocalDBChangeListener(this);
 
     // Deregister our base DNs.
@@ -371,24 +371,18 @@
       {
         TRACER.debugCaught(DebugLogLevel.ERROR, e);
       }
-      Message message = ERR_JEB_DATABASE_EXCEPTION.get(e.getMessage());
-      logError(message);
+      logError(ERR_JEB_DATABASE_EXCEPTION.get(e.getMessage()));
     }
 
     // Checksum this db environment and register its offline state id/checksum.
-    DirectoryServer.registerOfflineBackendStateID(this.getBackendID(),
-      checksumDbEnv());
-
-    //Deregister the alert generator.
+    DirectoryServer.registerOfflineBackendStateID(getBackendID(), checksumDbEnv());
     DirectoryServer.deregisterAlertGenerator(this);
 
     // Make sure the thread counts are zero for next initialization.
     threadTotalCount.set(0);
     threadWriteCount.set(0);
 
-    // Log an informational message.
-    Message message = NOTE_BACKEND_OFFLINE.get(cfg.getBackendId());
-    logError(message);
+    logError(NOTE_BACKEND_OFFLINE.get(cfg.getBackendId()));
   }
 
   /** {@inheritDoc} */
diff --git a/opends/src/server/org/opends/server/backends/task/TaskBackend.java b/opends/src/server/org/opends/server/backends/task/TaskBackend.java
index 105ea30..b99bb97 100644
--- a/opends/src/server/org/opends/server/backends/task/TaskBackend.java
+++ b/opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -268,9 +268,9 @@
   @Override
   public void finalizeBackend()
   {
+    super.finalizeBackend();
     currentConfig.removeTaskChangeListener(this);
 
-
     try
     {
       taskScheduler.stopScheduler();
diff --git a/opends/src/server/org/opends/server/core/PersistentSearch.java b/opends/src/server/org/opends/server/core/PersistentSearch.java
index 41b7d22..329cd94 100644
--- a/opends/src/server/org/opends/server/core/PersistentSearch.java
+++ b/opends/src/server/org/opends/server/core/PersistentSearch.java
@@ -34,13 +34,7 @@
 import org.opends.server.controls.EntryChangeNotificationControl;
 import org.opends.server.controls.PersistentSearchChangeType;
 import org.opends.server.loggers.debug.DebugTracer;
-import org.opends.server.types.CancelResult;
-import org.opends.server.types.Control;
-import org.opends.server.types.DN;
-import org.opends.server.types.DebugLogLevel;
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.Entry;
-import org.opends.server.types.ResultCode;
+import org.opends.server.types.*;
 
 import static org.opends.server.controls.PersistentSearchChangeType.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
@@ -113,10 +107,8 @@
       psearch.isCancelled = true;
 
       // The persistent search can no longer be cancelled.
-      psearch.searchOperation.getClientConnection().deregisterPersistentSearch(
-          psearch);
+      psearch.searchOperation.getClientConnection().deregisterPersistentSearch(psearch);
 
-      //Decrement of psearch count maintained by the server.
       DirectoryServer.deregisterPersistentSearch();
 
       // Notify any cancellation callbacks.
@@ -161,25 +153,33 @@
   /** The reference to the associated search operation. */
   private final SearchOperation searchOperation;
 
-
+  /**
+   * Indicates whether to only return entries that have been updated since the
+   * beginning of the search.
+   */
+  private final boolean changesOnly;
 
   /**
-   * Creates a new persistent search object with the provided
-   * information.
+   * Creates a new persistent search object with the provided information.
    *
    * @param searchOperation
    *          The search operation for this persistent search.
    * @param changeTypes
    *          The change types for which changes should be examined.
+   * @param changesOnly
+   *          whether to only return entries that have been updated since the
+   *          beginning of the search
    * @param returnECs
-   *          Indicates whether to include entry change notification
-   *          controls in search result entries sent to the client.
+   *          Indicates whether to include entry change notification controls in
+   *          search result entries sent to the client.
    */
   public PersistentSearch(SearchOperation searchOperation,
-      Set<PersistentSearchChangeType> changeTypes, boolean returnECs)
+      Set<PersistentSearchChangeType> changeTypes, boolean changesOnly,
+      boolean returnECs)
   {
     this.searchOperation = searchOperation;
     this.changeTypes = changeTypes;
+    this.changesOnly = changesOnly;
     this.returnECs = returnECs;
   }
 
@@ -241,6 +241,18 @@
   }
 
   /**
+   * Returns whether only entries updated after the beginning of this persistent
+   * search should be returned.
+   *
+   * @return true if only entries updated after the beginning of this search
+   *         should be returned, false otherwise
+   */
+  public boolean isChangesOnly()
+  {
+    return changesOnly;
+  }
+
+  /**
    * Notifies the persistent searches that an entry has been added.
    *
    * @param entry
diff --git a/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java b/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
index a0260c1..43d71d9 100644
--- a/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
+++ b/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -901,6 +901,7 @@
   @Override
   public void finalizeConfigHandler()
   {
+    finalizeBackend();
     try
     {
       DirectoryServer.deregisterBaseDN(configRootEntry.getDN());
@@ -916,13 +917,6 @@
 
   /** {@inheritDoc} */
   @Override
-  public void finalizeBackend()
-  {
-    // No implementation is required.
-  }
-
-  /** {@inheritDoc} */
-  @Override
   public ConfigEntry getConfigRootEntry()
          throws ConfigException
   {
diff --git a/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java b/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
index a0ebbc7..1477f0a 100644
--- a/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
+++ b/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -76,7 +76,6 @@
 import org.opends.server.types.operation.*;
 import org.opends.server.util.LDIFReader;
 import org.opends.server.util.TimeThread;
-import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
 import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation;
 
 import static org.opends.messages.ReplicationMessages.*;
@@ -475,7 +474,7 @@
     storeECLConfiguration(configuration);
     solveConflictFlag = isSolveConflict(configuration);
 
-    Backend backend = getBackend();
+    Backend<?> backend = getBackend();
     if (backend == null)
     {
       throw new ConfigException(ERR_SEARCHING_DOMAIN_BACKEND.get(
@@ -3490,7 +3489,7 @@
   private long exportBackend(OutputStream output, boolean checksumOutput)
       throws DirectoryException
   {
-    Backend backend = getBackend();
+    Backend<?> backend = getBackend();
 
     //  Acquire a shared lock for the backend.
     try
@@ -3623,7 +3622,7 @@
    * @throws DirectoryException
    *           If the backend could not be disabled or locked exclusively.
    */
-  private void preBackendImport(Backend backend) throws DirectoryException
+  private void preBackendImport(Backend<?> backend) throws DirectoryException
   {
     // Stop saving state
     stateSavingDisabled = true;
@@ -3653,10 +3652,9 @@
   @Override
   protected void importBackend(InputStream input) throws DirectoryException
   {
+    Backend<?> backend = getBackend();
+
     LDIFImportConfig importConfig = null;
-
-    Backend backend = getBackend();
-
     ImportExportContext ieCtx = getImportExportContext();
     try
     {
@@ -3742,7 +3740,7 @@
    * @param backend The backend implied in the import.
    * @exception DirectoryException Thrown when an error occurs.
    */
-  private void closeBackendImport(Backend backend) throws DirectoryException
+  private void closeBackendImport(Backend<?> backend) throws DirectoryException
   {
     String lockFile = LockFileManager.getBackendLockFileName(backend);
     StringBuilder failureReason = new StringBuilder();
@@ -3810,7 +3808,7 @@
    * Returns the backend associated to this domain.
    * @return The associated backend.
    */
-  private Backend getBackend()
+  private Backend<?> getBackend()
   {
     return DirectoryServer.getBackend(getBaseDN());
   }
@@ -4098,30 +4096,6 @@
 
     super.sessionInitiated(initStatus, rsState);
 
-    // Now that we are connected , we can enable ECL if :
-    // 1/ RS must in the same JVM and created an ECL_WORKFLOW_ELEMENT
-    // and 2/ this domain must NOT be private
-    if (!getBackend().isPrivateBackend())
-    {
-      try
-      {
-        ECLWorkflowElement wfe = (ECLWorkflowElement)
-        DirectoryServer.getWorkflowElement(
-            ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
-        if (wfe != null)
-        {
-          wfe.getReplicationServer().enableECL();
-        }
-      }
-      catch (DirectoryException de)
-      {
-        logError(NOTE_ERR_UNABLE_TO_ENABLE_ECL.get(
-            "Replication Domain on " + getBaseDNString(),
-            stackTraceToSingleLineString(de)));
-        // and go on
-      }
-    }
-
     // Now for bad data set status if needed
     if (forceBadDataSet)
     {
@@ -4375,7 +4349,7 @@
   @Override
   public long countEntries() throws DirectoryException
   {
-    Backend backend = getBackend();
+    Backend<?> backend = getBackend();
     if (!backend.supportsLDIFExport())
     {
       Message msg = ERR_INIT_EXPORT_NOT_SUPPORTED.get(backend.getBackendID());
diff --git a/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java b/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java
index 3d2e19a..bbd7a8f 100644
--- a/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java
+++ b/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java
@@ -34,12 +34,16 @@
 import org.opends.messages.Category;
 import org.opends.messages.Message;
 import org.opends.messages.Severity;
+import org.opends.server.backends.ChangelogBackend;
 import org.opends.server.replication.common.CSN;
 import org.opends.server.replication.common.MultiDomainServerState;
 import org.opends.server.replication.common.ServerState;
 import org.opends.server.replication.common.ServerStatus;
 import org.opends.server.replication.protocol.*;
-import org.opends.server.replication.server.changelog.api.*;
+import org.opends.server.replication.server.changelog.api.ChangeNumberIndexDB;
+import org.opends.server.replication.server.changelog.api.ChangeNumberIndexRecord;
+import org.opends.server.replication.server.changelog.api.ChangelogException;
+import org.opends.server.replication.server.changelog.api.DBCursor;
 import org.opends.server.types.*;
 import org.opends.server.util.ServerConstants;
 
@@ -47,10 +51,8 @@
 import static org.opends.server.loggers.ErrorLogger.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.replication.protocol.ProtocolVersion.*;
-import static org.opends.server.replication.protocol.StartECLSessionMsg
-.ECLRequestType.*;
-import static org.opends.server.replication.protocol.StartECLSessionMsg
-.Persistent.*;
+import static org.opends.server.replication.protocol.StartECLSessionMsg.ECLRequestType.*;
+import static org.opends.server.replication.protocol.StartECLSessionMsg.Persistent.*;
 import static org.opends.server.util.StaticUtils.*;
 
 /**
@@ -150,8 +152,13 @@
     private final ReplicationServerDomain rsDomain;
 
     /**
-     * Active when there are still changes supposed eligible for the ECL. It is
-     * active by default.
+     * Active when there are still changes supposed eligible for the ECL.
+     * Here is the lifecycle of this field:
+     * <ol>
+     * <li>active==true at the start of the INIT phase,</li>
+     * <li>active==false when there are no more changes for a domain in the the INIT phase,</li>
+     * <li>active==true if it is a persistent search on external changelog. It never moves again</li>
+     * </ol>
      */
     private boolean active = true;
     private UpdateMsg nextMsg;
@@ -349,8 +356,7 @@
     super(session, queueSize, replicationServer, rcvWindowSize);
     try
     {
-      DN baseDN = DN.decode(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
-      setBaseDNAndDomain(baseDN, true);
+      setBaseDNAndDomain(ChangelogBackend.CHANGELOG_BASE_DN, true);
     }
     catch(DirectoryException de)
     {
@@ -849,14 +855,6 @@
   }
 
   /**
-   * Registers this handler into its related domain and notifies the domain.
-   */
-  private void registerIntoDomain()
-  {
-    replicationServerDomain.registerHandler(this);
-  }
-
-  /**
    * Shutdown this handler.
    */
   @Override
@@ -867,16 +865,23 @@
       TRACER.debugInfo(this + " shutdown()");
     }
     releaseCursor();
-    for (DomainContext domainCtxt : domainCtxts) {
-      if (!domainCtxt.unRegisterHandler()) {
-        logError(Message.raw(Category.SYNC, Severity.NOTICE,
-            this + " shutdown() - error when unregistering handler "
-                + domainCtxt.mh));
+
+    if (domainCtxts != null)
+    {
+      for (DomainContext domainCtxt : domainCtxts)
+      {
+        if (!domainCtxt.unRegisterHandler())
+        {
+          logError(Message.raw(Category.SYNC, Severity.NOTICE, this
+              + " shutdown() - error when unregistering handler "
+              + domainCtxt.mh));
+        }
+        domainCtxt.stopServer();
       }
-      domainCtxt.stopServer();
+      domainCtxts = null;
     }
+
     super.shutdown();
-    domainCtxts = null;
   }
 
   private void releaseCursor()
@@ -1018,11 +1023,11 @@
       closeInitPhase();
     }
 
-    registerIntoDomain();
+    replicationServerDomain.registerHandler(this);
 
     if (debugEnabled())
     {
-      TRACER.debugInfo(getClass().getCanonicalName() + " " + getOperationId()
+      TRACER.debugInfo(getClass().getSimpleName() + " " + getOperationId()
           + " initialized: " + " " + dumpState() + domaimCtxtsToString(""));
     }
   }
@@ -1373,7 +1378,7 @@
           + dumpState());
     }
 
-    // go to persistent phase if one
+    // set all domains to be active again for the persistent phase
     for (DomainContext domainCtxt : domainCtxts) domainCtxt.active = true;
 
     if (startECLSessionMsg.getPersistent() != NON_PERSISTENT)
diff --git a/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java b/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java
index 6f9eb8d..5b50f6b 100644
--- a/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java
+++ b/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java
@@ -29,7 +29,7 @@
 import java.io.IOException;
 import java.net.SocketException;
 
-import org.opends.server.core.DirectoryServer;
+import org.opends.server.backends.ChangelogBackend;
 import org.opends.server.core.PersistentSearch;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.protocol.DoneMsg;
@@ -40,7 +40,6 @@
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.Entry;
 import org.opends.server.workflowelement.externalchangelog.ECLSearchOperation;
-import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
 
 import static org.opends.messages.ReplicationMessages.*;
 import static org.opends.server.loggers.ErrorLogger.*;
@@ -95,9 +94,8 @@
    */
   private PersistentSearch findPersistentSearch(ECLServerHandler handler)
   {
-    ECLWorkflowElement wfe = (ECLWorkflowElement)
-        DirectoryServer.getWorkflowElement(ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
-    for (PersistentSearch psearch : wfe.getPersistentSearches())
+    final ChangelogBackend backend = ChangelogBackend.getInstance();
+    for (PersistentSearch psearch : backend.getPersistentSearches())
     {
       if (psearch.getSearchOperation().toString().equals(
           handler.getOperationId()))
diff --git a/opends/src/server/org/opends/server/replication/server/ReplicationServer.java b/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
index 93a9f57..aa3120d 100644
--- a/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
+++ b/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
@@ -31,11 +31,8 @@
 import java.util.*;
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
 
-import org.opends.messages.Category;
 import org.opends.messages.Message;
-import org.opends.messages.Severity;
 import org.opends.server.admin.server.ConfigurationChangeListener;
 import org.opends.server.admin.std.meta.ReplicationServerCfgDefn.ReplicationDBImplementation;
 import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn.ConflictBehavior;
@@ -45,8 +42,6 @@
 import org.opends.server.backends.ChangelogBackend;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.WorkflowImpl;
-import org.opends.server.core.networkgroups.NetworkGroup;
 import org.opends.server.loggers.debug.DebugLogger;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.common.*;
@@ -61,9 +56,7 @@
 import org.opends.server.replication.server.changelog.je.JEChangelogDB;
 import org.opends.server.replication.service.DSRSShutdownSync;
 import org.opends.server.types.*;
-import org.opends.server.util.ServerConstants;
 import org.opends.server.util.StaticUtils;
-import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
 
 import static org.opends.messages.ConfigMessages.*;
 import static org.opends.messages.ReplicationMessages.*;
@@ -114,12 +107,6 @@
   /** The tracer object for the debug logger. */
   private static final DebugTracer TRACER = getTracer();
 
-  private static final String eclWorkflowID =
-    "External Changelog Workflow ID";
-  private ECLWorkflowElement eclwe;
-  private final AtomicReference<WorkflowImpl> eclWorkflowImpl =
-      new AtomicReference<WorkflowImpl>();
-
   /**
    * This is required for unit testing, so that we can keep track of all the
    * replication servers which are running in the VM.
@@ -178,6 +165,8 @@
     this.config = cfg;
     this.dsrsShutdownSync = dsrsShutdownSync;
     this.domainPredicate = predicate;
+
+    enableExternalChangeLog();
     ReplicationDBImplementation dbImpl = cfg.getReplicationDBImplementation();
     if (DebugLogger.debugEnabled())
     {
@@ -191,9 +180,6 @@
     initialize();
     cfg.addChangeListener(this);
 
-    // TODO : uncomment to branch changelog backend
-    //enableExternalChangeLog();
-
     localPorts.add(getReplicationPort());
 
     // Keep track of this new instance
@@ -464,15 +450,6 @@
       listenThread = new ReplicationServerListenThread(this);
       listenThread.start();
 
-      // Creates the ECL workflow elem so that DS (LDAPReplicationDomain)
-      // can know me and really enableECL.
-      if (WorkflowImpl.getWorkflow(eclWorkflowID) != null)
-      {
-        // Already done. Nothing to do
-        return;
-      }
-      eclwe = new ECLWorkflowElement(this);
-
       if (debugEnabled())
       {
         TRACER.debugInfo("RS " + getMonitorInstanceName()
@@ -486,51 +463,10 @@
       Message message = ERR_COULD_NOT_BIND_CHANGELOG.get(
           getReplicationPort(), e.getMessage());
       logError(message);
-    } catch (DirectoryException e)
-    {
-      //FIXME:DirectoryException is raised by initializeECL => fix err msg
-      Message message = Message.raw(Category.SYNC, Severity.SEVERE_ERROR,
-      "Directory Exception raised by ECL initialization: " + e.getMessage());
-      logError(message);
     }
   }
 
   /**
-   * Enable the ECL access by creating a dedicated workflow element.
-   * @throws DirectoryException when an error occurs.
-   */
-  public void enableECL() throws DirectoryException
-  {
-    if (eclWorkflowImpl.get() != null)
-    {
-      // ECL is already enabled, do nothing
-      return;
-    }
-
-    // Create the workflow for the base DN
-    // and register the workflow with the server.
-    final DN dn = DN.decode(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
-    final WorkflowImpl workflowImpl = new WorkflowImpl(eclWorkflowID, dn,
-        eclwe.getWorkflowElementID(), eclwe);
-    if (!eclWorkflowImpl.compareAndSet(null, workflowImpl))
-    {
-      // ECL is being enabled, do nothing
-      return;
-    }
-
-    workflowImpl.register();
-
-    NetworkGroup.getDefaultNetworkGroup().registerWorkflow(workflowImpl);
-
-    // FIXME:ECL should the ECL Workflow be registered in admin and internal
-    // network groups?
-    NetworkGroup.getAdminNetworkGroup().registerWorkflow(workflowImpl);
-    NetworkGroup.getInternalNetworkGroup().registerWorkflow(workflowImpl);
-
-    registerVirtualAttributeRules();
-  }
-
-  /**
    * Enable the external changelog if it is not already enabled.
    * <p>
    * The external changelog is provided by the changelog backend.
@@ -646,34 +582,6 @@
     }
   }
 
-  private void shutdownECL()
-  {
-    WorkflowImpl eclwf = (WorkflowImpl) WorkflowImpl.getWorkflow(eclWorkflowID);
-    // do it only if not already done by another RS (unit test case)
-    if (eclwf != null)
-    {
-      // FIXME:ECL should the ECL Workflow be registered in admin and internal
-      // network groups?
-      NetworkGroup.getInternalNetworkGroup().deregisterWorkflow(eclWorkflowID);
-      NetworkGroup.getAdminNetworkGroup().deregisterWorkflow(eclWorkflowID);
-
-      NetworkGroup.getDefaultNetworkGroup().deregisterWorkflow(eclWorkflowID);
-
-      deregisterVirtualAttributeRules();
-
-      eclwf.deregister();
-      eclwf.finalizeWorkflow();
-    }
-
-    eclwe = (ECLWorkflowElement) DirectoryServer
-        .getWorkflowElement("EXTERNAL CHANGE LOG");
-    if (eclwe != null)
-    {
-      DirectoryServer.deregisterWorkflowElement(eclwe);
-      eclwe.finalizeWorkflowElement();
-    }
-  }
-
   /**
    * Get the ReplicationServerDomain associated to the base DN given in
    * parameter.
@@ -844,9 +752,7 @@
       domain.shutdown();
     }
 
-    // TODO : switch to second method when changelog backend is branched
-    shutdownECL();
-    //shutdownExternalChangelog();
+    shutdownExternalChangelog();
 
     try
     {
diff --git a/opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java b/opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java
index 4db2e85..aea82ec 100644
--- a/opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java
+++ b/opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java
@@ -37,6 +37,7 @@
 import org.opends.messages.MessageBuilder;
 import org.opends.server.admin.std.server.ReplicationServerCfg;
 import org.opends.server.api.DirectoryThread;
+import org.opends.server.backends.ChangelogBackend;
 import org.opends.server.config.ConfigException;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.common.CSN;
@@ -797,6 +798,8 @@
     final FileReplicaDB replicaDB = pair.getFirst();
     replicaDB.add(updateMsg);
 
+    ChangelogBackend.getInstance().notifyEntryAdded(baseDN, 0, null, updateMsg);
+
     final ChangeNumberIndexer indexer = cnIndexer.get();
     if (indexer != null)
     {
diff --git a/opends/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexer.java b/opends/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexer.java
index a15c25d..b9a32b6 100644
--- a/opends/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexer.java
+++ b/opends/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexer.java
@@ -31,6 +31,7 @@
 
 import org.opends.messages.Message;
 import org.opends.server.api.DirectoryThread;
+import org.opends.server.backends.ChangelogBackend;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.common.CSN;
 import org.opends.server.replication.common.MultiDomainServerState;
@@ -491,9 +492,9 @@
           // OK, the oldest change is older than the medium consistency point
           // let's publish it to the CNIndexDB.
           final String previousCookie = mediumConsistencyRUV.toString();
-          final ChangeNumberIndexRecord record =
-              new ChangeNumberIndexRecord(previousCookie, baseDN, csn);
-          changelogDB.getChangeNumberIndexDB().addRecord(record);
+          final long changeNumber = changelogDB.getChangeNumberIndexDB().addRecord(
+              new ChangeNumberIndexRecord(previousCookie, baseDN, csn));
+          notifyEntryAddedToChangelog(baseDN, changeNumber, previousCookie, msg);
           moveForwardMediumConsistencyPoint(csn, baseDN);
         }
         catch (InterruptedException ignored)
@@ -523,6 +524,29 @@
   }
 
   /**
+   * Notifies the {@link ChangelogBackend} that a new entry has been added.
+   *
+   * @param baseDN
+   *          the baseDN of the newly added entry.
+   * @param changeNumber
+   *          the change number of the newly added entry. It will be greater
+   *          than zero for entries added to the change number index and less
+   *          than or equal to zero for entries added to any replica DB
+   * @param cookieString
+   *          a string representing the cookie of the newly added entry. This is
+   *          only meaningful for entries added to the change number index
+   * @param msg
+   *          the update message of the newly added entry
+   * @throws ChangelogException
+   *           If a problem occurs while notifying of the newly added entry.
+   */
+  protected void notifyEntryAddedToChangelog(DN baseDN, long changeNumber,
+      String cookieString, UpdateMsg msg) throws ChangelogException
+  {
+    ChangelogBackend.getInstance().notifyEntryAdded(baseDN, changeNumber, cookieString, msg);
+  }
+
+  /**
    * Nothing can be done about it.
    * <p>
    * Rely on the DirectoryThread uncaught exceptions handler for logging error +
diff --git a/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java b/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java
index 2706782..15d29b4 100644
--- a/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java
+++ b/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java
@@ -37,6 +37,7 @@
 import org.opends.messages.MessageBuilder;
 import org.opends.server.admin.std.server.ReplicationServerCfg;
 import org.opends.server.api.DirectoryThread;
+import org.opends.server.backends.ChangelogBackend;
 import org.opends.server.config.ConfigException;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.common.CSN;
@@ -846,6 +847,8 @@
     final JEReplicaDB replicaDB = pair.getFirst();
     replicaDB.add(updateMsg);
 
+    ChangelogBackend.getInstance().notifyEntryAdded(baseDN, 0, null, updateMsg);
+
     final ChangeNumberIndexer indexer = cnIndexer.get();
     if (indexer != null)
     {
diff --git a/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java b/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
index 8c4ab33..d0997db 100644
--- a/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
@@ -34,6 +34,7 @@
 import org.opends.messages.Severity;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.plugin.PluginResult;
+import org.opends.server.backends.ChangelogBackend;
 import org.opends.server.config.ConfigConstants;
 import org.opends.server.controls.*;
 import org.opends.server.core.*;
@@ -54,13 +55,12 @@
 
 import static org.opends.messages.CoreMessages.*;
 import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.backends.ChangelogBackend.*;
 import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.loggers.ErrorLogger.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
-import static org.opends.server.replication.protocol.StartECLSessionMsg
-.ECLRequestType.*;
-import static org.opends.server.replication.protocol.StartECLSessionMsg
-.Persistent.*;
+import static org.opends.server.replication.protocol.StartECLSessionMsg.ECLRequestType.*;
+import static org.opends.server.replication.protocol.StartECLSessionMsg.Persistent.*;
 import static org.opends.server.util.LDIFWriter.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
@@ -119,22 +119,6 @@
   private static final AttributeType MODIFIERS_NAME_TYPE =
       DirectoryConfig.getAttributeType(OP_ATTR_MODIFIERS_NAME_LC, true);
 
-
-  /** The associated DN. */
-  private static final DN CHANGELOG_ROOT_DN;
-  static
-  {
-    try
-    {
-      CHANGELOG_ROOT_DN = DN
-          .decode(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
-    }
-    catch (Exception e)
-    {
-      throw new RuntimeException(e);
-    }
-  }
-
   /**
    * The replication server in which the search on ECL is to be performed.
    */
@@ -300,7 +284,10 @@
       // If there's a persistent search, then register it with the server.
       if (persistentSearch != null)
       {
-        wfe.registerPersistentSearch(persistentSearch);
+        ChangelogBackend.getInstance().registerPersistentSearch(persistentSearch);
+        // TODO JNR Add callback on cancel,
+        // see ECLWorkflowElement.registerPersistentSearch().
+        // This will be removed very soon anyway.
         persistentSearch.enable();
       }
 
@@ -529,6 +516,7 @@
 
           persistentSearch = new PersistentSearch(this,
               psearchControl.getChangeTypes(),
+              psearchControl.getChangesOnly(),
               psearchControl.getReturnECs());
 
           // If we're only interested in changes, then we don't actually want
@@ -607,7 +595,7 @@
       ECLUpdateMsg update = eclServerHandler.getNextECLUpdate();
 
       // Return root entry if requested.
-      if (CHANGELOG_ROOT_DN.matchesBaseAndScope(baseDN, getScope()))
+      if (CHANGELOG_BASE_DN.matchesBaseAndScope(baseDN, getScope()))
       {
         final Entry entry = createRootEntry(update != null);
         if (filter.matchesEntry(entry) && !returnEntry(entry, null))
@@ -618,7 +606,7 @@
         }
       }
 
-      if (baseDN.equals(CHANGELOG_ROOT_DN)
+      if (baseDN.equals(CHANGELOG_BASE_DN)
           && getScope().equals(SearchScope.BASE_OBJECT))
       {
         // Only the change log root entry was requested. There is no need to
@@ -924,9 +912,9 @@
     addAttributeByUppercaseName("hassubordinates", "hasSubordinates",
         Boolean.toString(hasSubordinates), userAttrs, operationalAttrs);
     addAttributeByUppercaseName("entrydn", "entryDN",
-        CHANGELOG_ROOT_DN.toNormalizedString(), userAttrs, operationalAttrs);
+        DN_EXTERNAL_CHANGELOG_ROOT, userAttrs, operationalAttrs);
 
-    return new Entry(CHANGELOG_ROOT_DN, CHANGELOG_ROOT_OBJECT_CLASSES,
+    return new Entry(CHANGELOG_BASE_DN, CHANGELOG_ROOT_OBJECT_CLASSES,
         userAttrs, operationalAttrs);
   }
 
diff --git a/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java b/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
index 1f091e1..70206ad 100644
--- a/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
+++ b/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
@@ -22,49 +22,28 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
- *      Portions Copyright 2012 ForgeRock AS
+ *      Portions Copyright 2012-2014 ForgeRock AS
  */
 package org.opends.server.workflowelement.externalchangelog;
 
-
-
-import static org.opends.server.loggers.debug.DebugLogger.getTracer;
-
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.opends.server.admin.std.server.WorkflowElementCfg;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.PersistentSearch;
 import org.opends.server.core.SearchOperation;
-import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.server.ReplicationServer;
 import org.opends.server.types.CanceledOperationException;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.Operation;
 import org.opends.server.workflowelement.LeafWorkflowElement;
 
-
-
-
 /**
  * This class defines a workflow element for the external changelog (ECL);
  * e-g an entity that handles the processing of an operation against the ECL.
  */
-public class ECLWorkflowElement extends
-    LeafWorkflowElement<WorkflowElementCfg>
+public class ECLWorkflowElement extends LeafWorkflowElement<WorkflowElementCfg>
 {
-  /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = getTracer();
-
-  /**
-   *The set of persistent searches registered with this work flow element.
-   */
-  private final List<PersistentSearch> persistentSearches =
-    new CopyOnWriteArrayList<PersistentSearch>();
 
   /**
    * A string indicating the type of the workflow element.
@@ -75,7 +54,7 @@
    * The replication server object to which we will submits request
    * on the ECL. Retrieved from the local DirectoryServer.
    */
-  private ReplicationServer replicationServer;
+  private final ReplicationServer replicationServer;
 
   /**
    * Creates a new instance of the External Change Log workflow element.
@@ -91,26 +70,16 @@
     DirectoryServer.registerWorkflowElement(this);
   }
 
-  /**
-   * {@inheritDoc}
-   */
+  /** {@inheritDoc} */
   @Override
   public void finalizeWorkflowElement()
   {
-    // null all fields so that any use of the finalized object will raise
-    // an NPE
+    // null all fields so that any use of the finalized object will raise a NPE
     super.initialize(ECL_WORKFLOW_ELEMENT, null);
-
-    // Cancel all persistent searches.
-    for (PersistentSearch psearch : persistentSearches) {
-      psearch.cancel();
-    }
-    persistentSearches.clear();
   }
 
-  /**
-   * {@inheritDoc}
-   */
+  /** {@inheritDoc} */
+  @Override
   public void execute(Operation operation) throws CanceledOperationException {
     switch (operation.getOperationType())
     {
@@ -171,45 +140,6 @@
   }
 
   /**
-   * Registers the provided persistent search operation with this
-   * workflow element so that it will be notified of any
-   * add, delete, modify, or modify DN operations that are performed.
-   *
-   * @param persistentSearch
-   *          The persistent search operation to register with this
-   *          workflow element.
-   */
-  void registerPersistentSearch(PersistentSearch persistentSearch)
-  {
-    PersistentSearch.CancellationCallback callback =
-      new PersistentSearch.CancellationCallback()
-    {
-      public void persistentSearchCancelled(PersistentSearch psearch)
-      {
-        psearch.getSearchOperation().cancel(null);
-        persistentSearches.remove(psearch);
-      }
-    };
-
-    persistentSearches.add(persistentSearch);
-    persistentSearch.registerCancellationCallback(callback);
-  }
-
-
-
-  /**
-   * Gets the list of persistent searches currently active against
-   * this workflow element.
-   *
-   * @return The list of persistent searches currently active against
-   *         this workflow element.
-   */
-  public List<PersistentSearch> getPersistentSearches()
-  {
-    return persistentSearches;
-  }
-
-  /**
    * Returns the associated replication server.
    * @return the rs.
    */
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
index c8b7bf2..9a2f59a 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -184,7 +184,7 @@
         @Override
         public void run()
         {
-          for (PersistentSearch psearch : wfe.getPersistentSearches())
+          for (PersistentSearch psearch : backend.getPersistentSearches())
           {
             psearch.processAdd(entry);
           }
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
index 48900c1..56fcbfe 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -175,7 +175,7 @@
         @Override
         public void run()
         {
-          for (PersistentSearch psearch : wfe.getPersistentSearches())
+          for (PersistentSearch psearch : backend.getPersistentSearches())
           {
             psearch.processDelete(entry);
           }
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
index a54ae5e..7b390b5 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -203,7 +203,7 @@
         @Override
         public void run()
         {
-          for (PersistentSearch psearch : wfe.getPersistentSearches())
+          for (PersistentSearch psearch : backend.getPersistentSearches())
           {
             psearch.processModifyDN(newEntry, currentEntry.getDN());
           }
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index a503933..a1f8da5 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -317,7 +317,7 @@
         @Override
         public void run()
         {
-          for (PersistentSearch psearch : wfe.getPersistentSearches())
+          for (PersistentSearch psearch : backend.getPersistentSearches())
           {
             psearch.processModify(modifiedEntry, currentEntry);
           }
@@ -637,7 +637,7 @@
         Control c = iter.next();
         String  oid = c.getOID();
 
-        if (oid.equals(OID_LDAP_ASSERTION))
+        if (OID_LDAP_ASSERTION.equals(oid))
         {
           LDAPAssertionRequestControl assertControl =
                 getRequestControl(LDAPAssertionRequestControl.DECODER);
@@ -697,19 +697,19 @@
                                 de.getMessageObject()));
           }
         }
-        else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
+        else if (OID_LDAP_NOOP_OPENLDAP_ASSIGNED.equals(oid))
         {
           noOp = true;
         }
-        else if (oid.equals(OID_PERMISSIVE_MODIFY_CONTROL))
+        else if (OID_PERMISSIVE_MODIFY_CONTROL.equals(oid))
         {
           permissiveModify = true;
         }
-        else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
+        else if (OID_LDAP_READENTRY_PREREAD.equals(oid))
         {
           preReadRequest = getRequestControl(LDAPPreReadRequestControl.DECODER);
         }
-        else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
+        else if (OID_LDAP_READENTRY_POSTREAD.equals(oid))
         {
           if (c instanceof LDAPPostReadRequestControl)
           {
@@ -721,7 +721,7 @@
             iter.set(postReadRequest);
           }
         }
-        else if (oid.equals(OID_PROXIED_AUTH_V1))
+        else if (OID_PROXIED_AUTH_V1.equals(oid))
         {
           // Log usage of legacy proxy authz V1 control.
           addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
@@ -742,7 +742,7 @@
           setAuthorizationEntry(authorizationEntry);
           setProxiedAuthorizationDN(getDN(authorizationEntry));
         }
-        else if (oid.equals(OID_PROXIED_AUTH_V2))
+        else if (OID_PROXIED_AUTH_V2.equals(oid))
         {
           // The requester must have the PROXIED_AUTH privilege in order to
           // be able to use this control.
@@ -759,7 +759,7 @@
           setAuthorizationEntry(authorizationEntry);
           setProxiedAuthorizationDN(getDN(authorizationEntry));
         }
-        else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
+        else if (OID_PASSWORD_POLICY_CONTROL.equals(oid))
         {
           pwPolicyControlRequested = true;
         }
@@ -825,13 +825,11 @@
       // See if the attribute is one which controls the privileges available for
       // a user.  If it is, then the client must have the PRIVILEGE_CHANGE
       // privilege.
-      if (t.hasName(OP_ATTR_PRIVILEGE_NAME))
+      if (t.hasName(OP_ATTR_PRIVILEGE_NAME)
+          && !clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this))
       {
-        if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this))
-        {
-          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-                  ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
-        }
+        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
       }
 
       // If the modification is not updating the password attribute,
@@ -1065,11 +1063,11 @@
       numPasswords = passwordsToAdd;
     }
 
-    // If there were multiple password values, then make sure that's
-    // OK.
-    if ((!isInternalOperation())
-        && (!pwPolicyState.getAuthenticationPolicy()
-            .isAllowMultiplePasswordValues()) && (passwordsToAdd > 1))
+    // If there were multiple password values, then make sure that's OK.
+    final PasswordPolicy authPolicy = pwPolicyState.getAuthenticationPolicy();
+    if (!isInternalOperation()
+        && !authPolicy.isAllowMultiplePasswordValues()
+        && passwordsToAdd > 1)
     {
       pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
       throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
@@ -1085,9 +1083,8 @@
     {
       if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
       {
-        if ((!isInternalOperation())
-            && !pwPolicyState.getAuthenticationPolicy()
-                .isAllowPreEncodedPasswords())
+        if (!isInternalOperation()
+            && !authPolicy.isAllowPreEncodedPasswords())
         {
           pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
           throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
@@ -1100,15 +1097,13 @@
       }
       else
       {
-        if (m.getModificationType() == ModificationType.ADD)
+        if (m.getModificationType() == ModificationType.ADD
+            // Make sure that the password value does not already exist.
+            && pwPolicyState.passwordMatches(v.getValue()))
         {
-          // Make sure that the password value doesn't already exist.
-          if (pwPolicyState.passwordMatches(v.getValue()))
-          {
-            pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
-            throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
-                ERR_MODIFY_PASSWORD_EXISTS.get());
-          }
+          pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
+          throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
+              ERR_MODIFY_PASSWORD_EXISTS.get());
         }
 
         if (newPasswords == null)
@@ -1196,7 +1191,7 @@
       else
       {
         List<Attribute> attrList = currentEntry.getAttribute(pwAttr.getAttributeType());
-        if ((attrList == null) || (attrList.isEmpty()))
+        if (attrList == null || attrList.isEmpty())
         {
           throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
               ERR_MODIFY_NO_EXISTING_VALUES.get());
@@ -1214,51 +1209,37 @@
                     .decodeAuthPassword(av.getValue().toString());
                 PasswordStorageScheme<?> scheme = DirectoryServer
                     .getAuthPasswordStorageScheme(components[0].toString());
-                if (scheme != null)
+                if (scheme != null
+                    && scheme.authPasswordMatches(v.getValue(), components[1]
+                    .toString(), components[2].toString()))
                 {
-                  if (scheme.authPasswordMatches(v.getValue(), components[1]
-                      .toString(), components[2].toString()))
-                  {
-                    builder.add(av);
-                    found = true;
-                  }
-                }
-              }
-              else
-              {
-                if (av.equals(v))
-                {
-                  builder.add(v);
+                  builder.add(av);
                   found = true;
                 }
               }
+              else if (av.equals(v))
+              {
+                builder.add(v);
+                found = true;
+              }
             }
-            else
+            else if (UserPasswordSyntax.isEncoded(av.getValue()))
             {
-              if (UserPasswordSyntax.isEncoded(av.getValue()))
+              String[] components = UserPasswordSyntax.decodeUserPassword(av
+                  .getValue().toString());
+              PasswordStorageScheme<?> scheme = DirectoryServer
+                  .getPasswordStorageScheme(toLowerCase(components[0]));
+              if (scheme != null
+                  && scheme.passwordMatches(v.getValue(), ByteString.valueOf(components[1])))
               {
-                String[] components = UserPasswordSyntax.decodeUserPassword(av
-                    .getValue().toString());
-                PasswordStorageScheme<?> scheme = DirectoryServer
-                    .getPasswordStorageScheme(toLowerCase(components[0]));
-                if (scheme != null)
-                {
-                  if (scheme.passwordMatches(v.getValue(), ByteString.valueOf(
-                      components[1])))
-                  {
-                    builder.add(av);
-                    found = true;
-                  }
-                }
+                builder.add(av);
+                found = true;
               }
-              else
-              {
-                if (av.equals(v))
-                {
-                  builder.add(v);
-                  found = true;
-                }
-              }
+            }
+            else if (av.equals(v))
+            {
+              builder.add(v);
+              found = true;
             }
           }
         }
@@ -1425,7 +1406,7 @@
           TRACER.debugCaught(DebugLogLevel.ERROR, e);
         }
 
-        lowerName = toLowerCase(v.getValue().toString());
+        lowerName = toLowerCase(name);
       }
 
       ObjectClass oc = DirectoryServer.getObjectClass(lowerName);
@@ -1669,11 +1650,11 @@
     AttributeBuilder builder = new AttributeBuilder(a, true);
     for (AttributeValue existingValue : a)
     {
-      String s = existingValue.getValue().toString();
+      final String value = existingValue.getValue().toString();
       long currentValue;
       try
       {
-        currentValue = Long.parseLong(s);
+        currentValue = Long.parseLong(value);
       }
       catch (Exception e)
       {
@@ -1684,9 +1665,8 @@
 
         throw new DirectoryException(
             ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-            ERR_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE.get(String
-                .valueOf(entryDN), a.getName(),
-                existingValue.getValue().toString()),
+            ERR_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE.get(
+                String.valueOf(entryDN), a.getName(), value),
             e);
       }
 
@@ -1711,13 +1691,8 @@
   public void performAdditionalPasswordChangedProcessing()
          throws DirectoryException
   {
-    if (pwPolicyState == null)
-    {
-      // Account not managed locally so nothing to do.
-      return;
-    }
-
-    if (!passwordChanged)
+    if (!passwordChanged
+        || pwPolicyState == null) // Account not managed locally
     {
       // Nothing to do.
       return;
@@ -1748,85 +1723,63 @@
 
 
     // If any of the password values should be validated, then do so now.
-    if (selfChange || !authPolicy.isSkipValidationForAdministrators())
+    if (newPasswords != null
+        && (selfChange || !authPolicy.isSkipValidationForAdministrators()))
     {
-      if (newPasswords != null)
+      HashSet<ByteString> clearPasswords = new HashSet<ByteString>(pwPolicyState.getClearPasswords());
+      if (currentPasswords != null)
       {
-        HashSet<ByteString> clearPasswords = new HashSet<ByteString>();
-        clearPasswords.addAll(pwPolicyState.getClearPasswords());
-
-        if (currentPasswords != null)
+        if (clearPasswords.isEmpty())
         {
-          if (clearPasswords.isEmpty())
+          for (AttributeValue v : currentPasswords)
           {
-            for (AttributeValue v : currentPasswords)
-            {
-              clearPasswords.add(v.getValue());
-            }
-          }
-          else
-          {
-            // NOTE:  We can't rely on the fact that Set doesn't allow
-            // duplicates because technically it's possible that the values
-            // aren't duplicates if they are ASN.1 elements with different types
-            // (like 0x04 for a standard universal octet string type versus 0x80
-            // for a simple password in a bind operation).  So we have to
-            // manually check for duplicates.
-            for (AttributeValue v : currentPasswords)
-            {
-              ByteString pw = v.getValue();
-
-              boolean found = false;
-              for (ByteString s : clearPasswords)
-              {
-                if (s.equals(pw))
-                {
-                  found = true;
-                  break;
-                }
-              }
-
-              if (! found)
-              {
-                clearPasswords.add(pw);
-              }
-            }
+            clearPasswords.add(v.getValue());
           }
         }
-
-        for (AttributeValue v : newPasswords)
+        else
         {
-          MessageBuilder invalidReason = new MessageBuilder();
-          if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
-                                   v.getValue(), clearPasswords, invalidReason))
+          // NOTE:  We can't rely on the fact that Set doesn't allow
+          // duplicates because technically it's possible that the values
+          // aren't duplicates if they are ASN.1 elements with different types
+          // (like 0x04 for a standard universal octet string type versus 0x80
+          // for a simple password in a bind operation).  So we have to
+          // manually check for duplicates.
+          for (AttributeValue v : currentPasswords)
           {
-            pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-                ERR_MODIFY_PW_VALIDATION_FAILED.get(invalidReason));
+            clearPasswords.add(v.getValue());
           }
         }
       }
+
+      for (AttributeValue v : newPasswords)
+      {
+        MessageBuilder invalidReason = new MessageBuilder();
+        if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
+                                 v.getValue(), clearPasswords, invalidReason))
+        {
+          pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
+          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+              ERR_MODIFY_PW_VALIDATION_FAILED.get(invalidReason));
+        }
+      }
     }
 
 
     // If we should check the password history, then do so now.
-    if (pwPolicyState.maintainHistory())
+    if (newPasswords != null && pwPolicyState.maintainHistory())
     {
-      if (newPasswords != null)
+      for (AttributeValue v : newPasswords)
       {
-        for (AttributeValue v : newPasswords)
+        if (pwPolicyState.isPasswordInHistory(v.getValue())
+            && (selfChange || !authPolicy.isSkipValidationForAdministrators()))
         {
-          if (pwPolicyState.isPasswordInHistory(v.getValue())
-              && (selfChange || !authPolicy.isSkipValidationForAdministrators()))
-          {
-            pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-                ERR_MODIFY_PW_IN_HISTORY.get());
-          }
+          pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
+          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+              ERR_MODIFY_PW_IN_HISTORY.get());
         }
-
-        pwPolicyState.updatePasswordHistory();
       }
+
+      pwPolicyState.updatePasswordHistory();
     }
 
 
@@ -1882,7 +1835,7 @@
       return;
     }
 
-    if (!(passwordChanged || enabledStateChanged || wasLocked))
+    if (!passwordChanged && !enabledStateChanged && !wasLocked)
     {
       // Account managed locally, but unchanged, so nothing to do.
       return;
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
index 6b46f37..8d57883 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
@@ -54,46 +54,24 @@
        implements PreOperationSearchOperation, PostOperationSearchOperation,
                   SearchEntrySearchOperation, SearchReferenceSearchOperation
 {
-  /**
-   * The tracer object for the debug logger.
-   */
+  /** The tracer object for the debug logger. */
   private static final DebugTracer TRACER = getTracer();
 
+  /** The backend in which the search is to be performed. */
+  private Backend<?> backend;
 
-
-  /**
-   * The backend in which the search is to be performed.
-   */
-  private Backend backend;
-
-  /**
-   * Indicates whether we should actually process the search.  This should
-   * only be false if it's a persistent search with changesOnly=true.
-   */
-  private boolean processSearch;
-
-  /**
-   * The client connection for the search operation.
-   */
+  /** The client connection for the search operation. */
   private ClientConnection clientConnection;
 
-  /**
-   * The base DN for the search.
-   */
+  /** The base DN for the search. */
   private DN baseDN;
 
-  /**
-   * The persistent search request, if applicable.
-   */
+  /** The persistent search request, if applicable. */
   private PersistentSearch persistentSearch;
 
-  /**
-   * The filter for the search.
-   */
+  /** The filter for the search. */
   private SearchFilter filter;
 
-
-
   /**
    * Creates a new operation that may be used to search for entries in a local
    * backend of the Directory Server.
@@ -120,10 +98,7 @@
       throws CanceledOperationException
   {
     this.backend = wfe.getBackend();
-
-    clientConnection = getClientConnection();
-
-    processSearch = true;
+    this.clientConnection = getClientConnection();
 
     // Check for a request to cancel this operation.
     checkIfCanceled(false);
@@ -131,7 +106,7 @@
     try
     {
       BooleanHolder executePostOpPlugins = new BooleanHolder(false);
-      processSearch(wfe, executePostOpPlugins);
+      processSearch(executePostOpPlugins);
 
       // Check for a request to cancel this operation.
       checkIfCanceled(false);
@@ -157,8 +132,7 @@
     }
   }
 
-  private void processSearch(LocalBackendWorkflowElement wfe,
-      BooleanHolder executePostOpPlugins) throws CanceledOperationException
+  private void processSearch(BooleanHolder executePostOpPlugins) throws CanceledOperationException
   {
     // Process the search base and filter to convert them from their raw forms
     // as provided by the client to the forms required for the rest of the
@@ -166,7 +140,7 @@
     baseDN = getBaseDN();
     filter = getFilter();
 
-    if ((baseDN == null) || (filter == null))
+    if (baseDN == null || filter == null)
     {
       return;
     }
@@ -253,8 +227,13 @@
 
 
     // If there's a persistent search, then register it with the server.
+    boolean processSearchNow = true;
     if (persistentSearch != null)
     {
+      // If we're only interested in changes, then we do not actually want
+      // to process the search now.
+      processSearchNow = !persistentSearch.isChangesOnly();
+
       // The Core server maintains the count of concurrent persistent searches
       // so that all the backends (Remote and Local) are aware of it. Verify
       // with the core if we have already reached the threshold.
@@ -264,7 +243,7 @@
         appendErrorMessage(ERR_MAX_PSEARCH_LIMIT_EXCEEDED.get());
         return;
       }
-      wfe.registerPersistentSearch(persistentSearch);
+      backend.registerPersistentSearch(persistentSearch);
       persistentSearch.enable();
     }
 
@@ -272,7 +251,7 @@
     // Process the search in the backend and all its subordinates.
     try
     {
-      if (processSearch)
+      if (processSearchNow)
       {
         backend.search(this);
       }
@@ -335,14 +314,13 @@
     LocalBackendWorkflowElement.removeAllDisallowedControls(baseDN, this);
 
     List<Control> requestControls  = getRequestControls();
-    if ((requestControls != null) && (! requestControls.isEmpty()))
+    if (requestControls != null && ! requestControls.isEmpty())
     {
-      for (int i=0; i < requestControls.size(); i++)
+      for (Control c : requestControls)
       {
-        Control c   = requestControls.get(i);
         String  oid = c.getOID();
 
-        if (oid.equals(OID_LDAP_ASSERTION))
+        if (OID_LDAP_ASSERTION.equals(oid))
         {
           LDAPAssertionRequestControl assertControl =
                 getRequestControl(LDAPAssertionRequestControl.DECODER);
@@ -421,7 +399,7 @@
                                 de.getMessageObject()), de);
           }
         }
-        else if (oid.equals(OID_PROXIED_AUTH_V1))
+        else if (OID_PROXIED_AUTH_V1.equals(oid))
         {
           // Log usage of legacy proxy authz V1 control.
           addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
@@ -440,16 +418,9 @@
 
           Entry authorizationEntry = proxyControl.getAuthorizationEntry();
           setAuthorizationEntry(authorizationEntry);
-          if (authorizationEntry == null)
-          {
-            setProxiedAuthorizationDN(DN.nullDN());
-          }
-          else
-          {
-            setProxiedAuthorizationDN(authorizationEntry.getDN());
-          }
+          setProxiedAuthorizationDN(getDN(authorizationEntry));
         }
-        else if (oid.equals(OID_PROXIED_AUTH_V2))
+        else if (OID_PROXIED_AUTH_V2.equals(oid))
         {
           // The requester must have the PROXIED_AUTH privilege in order to be
           // able to use this control.
@@ -464,38 +435,23 @@
 
           Entry authorizationEntry = proxyControl.getAuthorizationEntry();
           setAuthorizationEntry(authorizationEntry);
-          if (authorizationEntry == null)
-          {
-            setProxiedAuthorizationDN(DN.nullDN());
-          }
-          else
-          {
-            setProxiedAuthorizationDN(authorizationEntry.getDN());
-          }
+          setProxiedAuthorizationDN(getDN(authorizationEntry));
         }
-        else if (oid.equals(OID_PERSISTENT_SEARCH))
+        else if (OID_PERSISTENT_SEARCH.equals(oid))
         {
-          PersistentSearchControl psearchControl =
-            getRequestControl(PersistentSearchControl.DECODER);
+          final PersistentSearchControl ctrl =
+              getRequestControl(PersistentSearchControl.DECODER);
 
           persistentSearch = new PersistentSearch(this,
-                                      psearchControl.getChangeTypes(),
-                                      psearchControl.getReturnECs());
-
-          // If we're only interested in changes, then we don't actually want
-          // to process the search now.
-          if (psearchControl.getChangesOnly())
-          {
-            processSearch = false;
-          }
+              ctrl.getChangeTypes(), ctrl.getChangesOnly(), ctrl.getReturnECs());
         }
-        else if (oid.equals(OID_LDAP_SUBENTRIES))
+        else if (OID_LDAP_SUBENTRIES.equals(oid))
         {
           SubentriesControl subentriesControl =
                   getRequestControl(SubentriesControl.DECODER);
           setReturnSubentriesOnly(subentriesControl.getVisibility());
         }
-        else if (oid.equals(OID_LDUP_SUBENTRIES))
+        else if (OID_LDUP_SUBENTRIES.equals(oid))
         {
           // Support for legacy draft-ietf-ldup-subentry.
           addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
@@ -503,25 +459,25 @@
 
           setReturnSubentriesOnly(true);
         }
-        else if (oid.equals(OID_MATCHED_VALUES))
+        else if (OID_MATCHED_VALUES.equals(oid))
         {
           MatchedValuesControl matchedValuesControl =
                 getRequestControl(MatchedValuesControl.DECODER);
           setMatchedValuesControl(matchedValuesControl);
         }
-        else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
+        else if (OID_ACCOUNT_USABLE_CONTROL.equals(oid))
         {
           setIncludeUsableControl(true);
         }
-        else if (oid.equals(OID_REAL_ATTRS_ONLY))
+        else if (OID_REAL_ATTRS_ONLY.equals(oid))
         {
           setRealAttributesOnly(true);
         }
-        else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
+        else if (OID_VIRTUAL_ATTRS_ONLY.equals(oid))
         {
           setVirtualAttributesOnly(true);
         }
-        else if (oid.equals(OID_GET_EFFECTIVE_RIGHTS) &&
+        else if (OID_GET_EFFECTIVE_RIGHTS.equals(oid) &&
           DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS))
         {
           // Do nothing here and let AciHandler deal with it.
@@ -538,6 +494,11 @@
     }
   }
 
+  private DN getDN(Entry e)
+  {
+    return e != null ? e.getDN() : DN.nullDN();
+  }
+
   /** Indicates if the backend supports the control corresponding to provided oid. */
   private boolean backendSupportsControl(final String oid)
   {
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
index 0dd0f22..45134e4 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -22,7 +22,7 @@
  *
  *
  *      Copyright 2008-2010 Sun Microsystems, Inc.
- *      Portions Copyright 2011-2013 ForgeRock AS
+ *      Portions Copyright 2011-2014 ForgeRock AS
  */
 package org.opends.server.workflowelement.localbackend;
 
@@ -30,7 +30,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.TreeMap;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.opends.messages.Message;
 import org.opends.messages.MessageDescriptor;
@@ -68,7 +67,7 @@
   private static final DebugTracer TRACER = getTracer();
 
   /** the backend associated with the local workflow element. */
-  private Backend backend;
+  private Backend<?> backend;
 
 
   /** the set of local backend workflow elements registered with the server. */
@@ -77,13 +76,7 @@
             new TreeMap<String, LocalBackendWorkflowElement>();
 
   /**
-   * The set of persistent searches registered with this work flow element.
-   */
-  private final List<PersistentSearch> persistentSearches =
-    new CopyOnWriteArrayList<PersistentSearch>();
-
-  /**
-   * a lock to guarantee safe concurrent access to the registeredLocalBackends
+   * A lock to guarantee safe concurrent access to the registeredLocalBackends
    * variable.
    */
   private static final Object registeredLocalBackendsLock = new Object();
@@ -112,9 +105,8 @@
    * @param workflowElementID  the workflow element identifier
    * @param backend  the backend associated to that workflow element
    */
-  private void initialize(String workflowElementID, Backend backend)
+  private void initialize(String workflowElementID, Backend<?> backend)
   {
-    // Initialize the workflow ID
     super.initialize(workflowElementID, BACKEND_WORKFLOW_ELEMENT);
 
     this.backend  = backend;
@@ -154,29 +146,16 @@
     processWorkflowElementConfig(configuration, true);
   }
 
-
-  /**
-   * {@inheritDoc}
-   */
+  /** {@inheritDoc} */
   @Override
   public void finalizeWorkflowElement()
   {
-    // null all fields so that any use of the finalized object will raise
-    // an NPE
+    // null all fields so that any use of the finalized object will raise a NPE
     super.initialize(null, null);
     backend = null;
-
-    // Cancel all persistent searches.
-    for (PersistentSearch psearch : persistentSearches) {
-      psearch.cancel();
-    }
-    persistentSearches.clear();
   }
 
-
-  /**
-   * {@inheritDoc}
-   */
+  /** {@inheritDoc} */
   @Override
   public boolean isConfigurationChangeAcceptable(
       LocalBackendWorkflowElementCfg configuration,
@@ -186,10 +165,7 @@
     return processWorkflowElementConfig(configuration, false);
   }
 
-
-  /**
-   * {@inheritDoc}
-   */
+  /** {@inheritDoc} */
   @Override
   public ConfigChangeResult applyConfigurationChange(
       LocalBackendWorkflowElementCfg configuration
@@ -224,7 +200,7 @@
     {
       // Read configuration.
       String newBackendID = configuration.getBackend();
-      Backend newBackend  = DirectoryServer.getBackend(newBackendID);
+      Backend<?> newBackend = DirectoryServer.getBackend(newBackendID);
 
       // If the backend is null (i.e. not found in the list of
       // registered backends, this is probably because we are looking
@@ -273,8 +249,7 @@
    *         element.
    */
   public static LocalBackendWorkflowElement createAndRegister(
-      String workflowElementID,
-      Backend backend)
+      String workflowElementID, Backend<?> backend)
   {
     // If the requested workflow element does not exist then create one.
     LocalBackendWorkflowElement localBackend =
@@ -661,11 +636,7 @@
     }
   }
 
-
-
-  /**
-   * {@inheritDoc}
-   */
+  /** {@inheritDoc} */
   @Override
   public void execute(Operation operation) throws CanceledOperationException {
     switch (operation.getOperationType())
@@ -766,54 +737,11 @@
    * @return The backend associated with this local backend workflow
    *         element.
    */
-  public Backend getBackend()
+  public Backend<?> getBackend()
   {
     return backend;
   }
 
-
-
-  /**
-   * Registers the provided persistent search operation with this
-   * local backend workflow element so that it will be notified of any
-   * add, delete, modify, or modify DN operations that are performed.
-   *
-   * @param persistentSearch
-   *          The persistent search operation to register with this
-   *          local backend workflow element.
-   */
-  void registerPersistentSearch(PersistentSearch persistentSearch)
-  {
-    PersistentSearch.CancellationCallback callback =
-      new PersistentSearch.CancellationCallback()
-    {
-      @Override
-      public void persistentSearchCancelled(PersistentSearch psearch)
-      {
-        persistentSearches.remove(psearch);
-      }
-    };
-
-    persistentSearches.add(persistentSearch);
-    persistentSearch.registerCancellationCallback(callback);
-  }
-
-
-
-  /**
-   * Gets the list of persistent searches currently active against
-   * this local backend workflow element.
-   *
-   * @return The list of persistent searches currently active against
-   *         this local backend workflow element.
-   */
-  List<PersistentSearch> getPersistentSearches()
-  {
-    return persistentSearches;
-  }
-
-
-
   /**
    * Checks if an update operation can be performed against a backend. The
    * operation will be rejected based on the server and backend writability
@@ -834,7 +762,7 @@
    * @throws DirectoryException
    *           If the update operation has been rejected.
    */
-  static void checkIfBackendIsWritable(Backend backend, Operation op,
+  static void checkIfBackendIsWritable(Backend<?> backend, Operation op,
       DN entryDN, MessageDescriptor.Arg1<CharSequence> serverMsg,
       MessageDescriptor.Arg1<CharSequence> backendMsg)
       throws DirectoryException
@@ -870,5 +798,14 @@
       }
     }
   }
-}
 
+  /** {@inheritDoc} */
+  @Override
+  public String toString()
+  {
+    return getClass().getSimpleName()
+        + " backend=" + backend
+        + " workflowElementID=" + getWorkflowElementID()
+        + " workflowElementTypeInfo=" + getWorkflowElementTypeInfo();
+  }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java
index 1e667a9..e1d6127 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java
@@ -25,16 +25,6 @@
  */
 package org.opends.server.backends;
 
-import static org.assertj.core.api.Assertions.*;
-import static org.opends.messages.ReplicationMessages.*;
-import static org.opends.server.TestCaseUtils.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import static org.opends.server.replication.protocol.OperationContext.*;
-import static org.opends.server.types.ResultCode.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-import static org.testng.Assert.*;
-
 import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -108,6 +98,17 @@
 
 import com.forgerock.opendj.util.Pair;
 
+import static org.assertj.core.api.Assertions.*;
+import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.TestCaseUtils.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.replication.protocol.OperationContext.*;
+import static org.opends.server.types.ResultCode.*;
+import static org.opends.server.util.CollectionUtils.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+import static org.testng.Assert.*;
+
 @SuppressWarnings("javadoc")
 public class ChangelogBackendTestCase extends ReplicationTestCase
 {
@@ -241,7 +242,7 @@
     }
   }
 
-  @Test(enabled=false)
+  @Test
   public void searchInCookieModeOnOneSuffixUsingEmptyCookie() throws Exception
   {
     String test = "EmptyCookie";
@@ -268,7 +269,7 @@
     debugInfo(test, "Ending search with success");
   }
 
-  @Test(enabled=false)
+  @Test
   public void searchInCookieModeOnOneSuffix() throws Exception
   {
     String test = "CookieOneSuffix";
@@ -305,7 +306,6 @@
     searchOp = searchChangelogUsingCookie("(targetdn=*" + test + "*,o=test)", cookies[3], nbEntries, SUCCESS, test);
 
     debugInfo(test, "Ending search with success");
-
   }
 
   @Test(enabled=false)
@@ -520,7 +520,7 @@
       debugInfo(test, "Ending test successfully");
   }
 
-  @Test(enabled=false)
+  @Test
   public void searchInDraftModeWithInvalidChangeNumber() throws Exception
   {
     String testName = "UnknownChangeNumber";
@@ -531,7 +531,7 @@
     debugInfo(testName, "Ending test with success");
   }
 
-  @Test(enabled=false)
+  @Test
   public void searchInDraftModeOnOneSuffix() throws Exception
   {
     long firstChangeNumber = 1;
@@ -547,7 +547,7 @@
     debugInfo(testName, "Ending search with success");
   }
 
-  @Test(enabled=false)
+  @Test
   public void searchInDraftModeOnOneSuffixMultipleTimes() throws Exception
   {
     replicationServer.getChangelogDB().setPurgeDelay(0);
@@ -582,7 +582,7 @@
   /**
    * Verifies that is not possible to read the changelog without the changelog-read privilege
    */
-  @Test(enabled=false)
+  @Test
   public void searchingWithoutPrivilegeShouldFail() throws Exception
   {
     AuthenticationInfo nonPrivilegedUser = new AuthenticationInfo();
@@ -594,7 +594,7 @@
     assertEquals(op.getErrorMessage().toMessage(), NOTE_SEARCH_CHANGELOG_INSUFFICIENT_PRIVILEGES.get());
   }
 
-  @Test(enabled=false)
+  @Test
   public void persistentSearch() throws Exception
   {
    // TODO
@@ -603,7 +603,7 @@
    // ExternalChangeLogTest#ECLReplicationServerFullTest16
   }
 
-  @Test(enabled=false)
+  @Test
   public void simultaneousPersistentSearches() throws Exception
   {
     // TODO
@@ -624,7 +624,7 @@
   /**
    * With an empty RS, a search should return only root entry.
    */
-  @Test(enabled=false)
+  @Test
   public void searchWhenNoChangesShouldReturnRootEntryOnly() throws Exception
   {
     String testName = "EmptyRS";
@@ -635,7 +635,7 @@
     debugInfo(testName, "Ending test successfully");
   }
 
-  @Test(enabled=false)
+  @Test
   public void operationalAndVirtualAttributesShouldNotBeVisibleOutsideRootDSE() throws Exception
   {
     String testName = "attributesVisibleOutsideRootDSE";
@@ -1082,7 +1082,7 @@
   private List<Modification> createAttributeModif(String attributeName, String valueString)
   {
     Attribute attr = Attributes.create(attributeName, valueString);
-    return newList(new Modification(ModificationType.REPLACE, attr));
+    return newArrayList(new Modification(ModificationType.REPLACE, attr));
   }
 
   private UpdateMsg generateModDNMsg(String baseDn, CSN csn, String testName) throws Exception
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java
index 63d9c92..275450d 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java
@@ -29,6 +29,7 @@
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.StringReader;
+import java.lang.reflect.Method;
 import java.net.Socket;
 import java.util.*;
 
@@ -65,7 +66,6 @@
 import org.opends.server.util.LDIFWriter;
 import org.opends.server.util.TimeThread;
 import org.opends.server.workflowelement.externalchangelog.ECLSearchOperation;
-import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
 import org.opends.server.workflowelement.localbackend.LocalBackendModifyDNOperation;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
@@ -79,6 +79,7 @@
 import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.replication.protocol.OperationContext.*;
 import static org.opends.server.types.ResultCode.*;
+import static org.opends.server.util.CollectionUtils.*;
 import static org.opends.server.util.StaticUtils.*;
 import static org.testng.Assert.*;
 
@@ -129,7 +130,7 @@
    * When used in a search operation, it includes all attributes (user and
    * operational)
    */
-  private static final Set<String> ALL_ATTRIBUTES = newSet("*", "+");
+  private static final Set<String> ALL_ATTRIBUTES = newHashSet("*", "+");
   private static final List<Control> NO_CONTROL = null;
 
   /**
@@ -172,14 +173,6 @@
   public void PrimaryTest() throws Exception
   {
     replicationServer.getChangelogDB().setPurgeDelay(0);
-    // let's enable ECl manually now that we tested that ECl is not available
-    ECLWorkflowElement wfe =
-        (ECLWorkflowElement) DirectoryServer
-        .getWorkflowElement(ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
-    if (wfe != null)
-    {
-      wfe.getReplicationServer().enableECL();
-    }
 
     // Test all types of ops.
     ECLAllOps(); // Do not clean the db for the next test
@@ -346,7 +339,7 @@
   /**
    * Verifies that is not possible to read the changelog without the changelog-read privilege
    */
-  @Test(enabled=true, dependsOnMethods = { "PrimaryTest"})
+  @Test(enabled = false, dependsOnMethods = { "PrimaryTest" })
   public void ECLChangelogReadPrivilegeTest() throws Exception
   {
     AuthenticationInfo nonPrivilegedUser = new AuthenticationInfo();
@@ -362,7 +355,22 @@
   @Test(enabled = true)
   public void TestECLIsNotASupportedSuffix() throws Exception
   {
-    ECLCompatTestLimits(0,0, false);
+    try
+    {
+      invoke(replicationServer, "shutdownExternalChangelog");
+      ECLCompatTestLimits(0, 0, false);
+    }
+    finally
+    {
+      invoke(replicationServer, "enableExternalChangeLog");
+    }
+  }
+
+  private void invoke(Object obj, String methodName) throws Exception
+  {
+    final Method m = obj.getClass().getDeclaredMethod(methodName);
+    m.setAccessible(true);
+    m.invoke(obj);
   }
 
   /**
@@ -555,7 +563,7 @@
     ReplicationBroker server01 = null;
     LDAPReplicationDomain domain = null;
     LDAPReplicationDomain domain2 = null;
-    Backend backend2 = null;
+    Backend<?> backend2 = null;
 
     // Use different values than other tests to avoid test interactions in concurrent test runs
     final String backendId2 = tn + 2;
@@ -636,7 +644,7 @@
     ReplicationBroker s2test = null;
     ReplicationBroker s2test2 = null;
 
-    Backend backend2 = null;
+    Backend<?> backend2 = null;
     LDAPReplicationDomain domain1 = null;
     LDAPReplicationDomain domain2 = null;
     try
@@ -948,7 +956,7 @@
   }
 
   /** Test ECL content after a domain has been removed. */
-  @Test(enabled=true, dependsOnMethods = { "PrimaryTest"})
+  @Test(enabled = false, dependsOnMethods = { "PrimaryTest" })
   public void testECLAfterDomainIsRemoved() throws Exception
   {
     String testName = "testECLAfterDomainIsRemoved";
@@ -1045,9 +1053,8 @@
     String cookie = "";
     LDIFWriter ldifWriter = getLDIFWriter();
 
-    Set<String> lastcookieattribute = newSet("lastExternalChangelogCookie");
-    InternalSearchOperation searchOp = searchOnRootDSE(lastcookieattribute);
-    List<SearchResultEntry> entries = searchOp.getSearchEntries();
+    final Set<String> attrs = newHashSet("lastExternalChangelogCookie");
+    List<SearchResultEntry> entries = searchOnRootDSE(attrs).getSearchEntries();
     if (entries != null)
     {
       for (SearchResultEntry resultEntry : entries)
@@ -1149,7 +1156,7 @@
         checkValue(resultEntry, "replicationcsn", csns[i - 1].toString());
         checkValue(resultEntry, "replicaidentifier", String.valueOf(SERVER_ID_1));
         checkValue(resultEntry, "changelogcookie", cookies[i - 1]);
-        checkValue(resultEntry, "changenumber", "0");
+        assertNull(getAttributeValue(resultEntry, "changenumber"));
 
         if (i==1)
         {
@@ -1342,8 +1349,7 @@
     return av.toString();
   }
 
-  private static void checkValues(Entry entry, String attrName,
-      Set<String> expectedValues)
+  private static void checkValues(Entry entry, String attrName, Set<String> expectedValues)
   {
     final Set<String> values = new HashSet<String>();
     for (Attribute a : entry.getAttribute(attrName))
@@ -1931,7 +1937,7 @@
   /**
    * Utility - create a second backend in order to test ECL with 2 suffixes.
    */
-  private static Backend initializeTestBackend(boolean createBaseEntry,
+  private static Backend<?> initializeTestBackend(boolean createBaseEntry,
       String backendId) throws Exception
   {
     DN baseDN = DN.decode("o=" + backendId);
@@ -1963,9 +1969,9 @@
     return memoryBackend;
   }
 
-  private static void removeTestBackend(Backend... backends)
+  private static void removeTestBackend(Backend<?>... backends)
   {
-    for (Backend backend : backends)
+    for (Backend<?> backend : backends)
     {
       if (backend != null)
       {
@@ -1989,7 +1995,7 @@
     ReplicationBroker s2test = null;
     ReplicationBroker s1test2 = null;
     ReplicationBroker s2test2 = null;
-    Backend backend2 = null;
+    Backend<?> backend2 = null;
 
     try
     {
@@ -2434,7 +2440,7 @@
     // available in other entries. We u
     debugInfo(tn, "Starting test \n\n");
 
-    Set<String> attributes = newSet("firstchangenumber", "lastchangenumber",
+    Set<String> attributes = newHashSet("firstchangenumber", "lastchangenumber",
         "changelog", "lastExternalChangelogCookie");
 
     debugInfo(tn, " Search: " + TEST_ROOT_DN_STRING);
@@ -2603,8 +2609,8 @@
 
     final String backendId3 = "test3";
     final DN baseDN3 = DN.decode("o=" + backendId3);
-    Backend backend2 = null;
-    Backend backend3 = null;
+    Backend<?> backend2 = null;
+    Backend<?> backend3 = null;
     LDAPReplicationDomain domain2 = null;
     LDAPReplicationDomain domain3 = null;
     LDAPReplicationDomain domain21 = null;
@@ -2702,7 +2708,7 @@
         {
           Entry targetEntry = parseIncludedAttributes(resultEntry, targetdn);
 
-          Set<String> eoc = newSet("person", "inetOrgPerson", "organizationalPerson", "top");
+          Set<String> eoc = newHashSet("person", "inetOrgPerson", "organizationalPerson", "top");
           checkValues(targetEntry, "objectclass", eoc);
 
           String changeType = getAttributeValue(resultEntry, "changetype");
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexerTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexerTest.java
index f03b027..99bf49d 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexerTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexerTest.java
@@ -41,10 +41,7 @@
 import org.opends.server.replication.common.ServerState;
 import org.opends.server.replication.protocol.UpdateMsg;
 import org.opends.server.replication.server.ChangelogState;
-import org.opends.server.replication.server.changelog.api.ChangeNumberIndexDB;
-import org.opends.server.replication.server.changelog.api.ChangeNumberIndexRecord;
-import org.opends.server.replication.server.changelog.api.ChangelogDB;
-import org.opends.server.replication.server.changelog.api.ReplicationDomainDB;
+import org.opends.server.replication.server.changelog.api.*;
 import org.opends.server.types.DN;
 import org.testng.annotations.*;
 
@@ -635,7 +632,16 @@
         return eclEnabledDomains.contains(baseDN);
       }
     };
-    cnIndexer = new ChangeNumberIndexer(changelogDB, initialState, predicate);
+    cnIndexer = new ChangeNumberIndexer(changelogDB, initialState, predicate)
+    {
+      /** {@inheritDoc} */
+      @Override
+      protected void notifyEntryAddedToChangelog(DN baseDN, long changeNumber,
+          String previousCookie, UpdateMsg msg) throws ChangelogException
+      {
+        // avoid problems with ChangelogBackend initialization
+      }
+    };
     cnIndexer.start();
     waitForWaitingState(cnIndexer);
   }

--
Gitblit v1.10.0