From 5ec0cb08889c9f1a24fd4cc8b139dcdb942dd92a Mon Sep 17 00:00:00 2001
From: pgamba <pgamba@localhost>
Date: Fri, 14 Aug 2009 12:37:19 +0000
Subject: [PATCH] Support for External change log compatible with draft-good-ldap-changelog-04.txt , March 2003

---
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java                 |    6 
 opendj-sdk/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java                                |    3 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java     |    7 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java                              |  401 ++
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java                                    |  143 +
 opendj-sdk/opends/resource/config/config.ldif                                                                               |   22 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ExternalChangeLogTest.java               | 1884 ++++++++++++-
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java                                       |   45 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeStressReplicationDomain.java |    6 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/AssuredReplicationServerTest.java |    2 
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml                             |   24 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDbIterator.java                                    |  178 +
 opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java                    |  551 ++-
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeReplicationDomain.java       |    4 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java    |    2 
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ChangeTimeHeartbeatMsg.java                             |  148 +
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java                                   |    5 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDbHandler.java                                     |  548 ++++
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java                                     | 1510 +++++-----
 opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationDomain.java                                   |    8 
 opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java                               |   59 
 opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java                                                   |    3 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/StateMachineTest.java             |    2 
 opendj-sdk/opends/resource/schema/00-core.ldif                                                                              |    4 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/DraftCNDbHandlerTest.java         |  299 ++
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java                                          |   24 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java                                        |   11 
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java                                     |    6 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java                                         |    9 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationBackend.java                                   |    6 
 opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java                                          |    3 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java                             |   15 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java                |    8 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/DbHandler.java                                            |   18 
 opendj-sdk/opends/src/messages/messages/replication.properties                                                              |    3 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationDraftCNKey.java                                |   68 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationDbEnv.java                                     |   23 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java |    8 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNData.java                                          |  173 +
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationIterator.java                                  |    8 
 opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java                                   |   81 
 opendj-sdk/opends/src/server/org/opends/server/replication/service/CTHeartbeatPublisherThread.java                          |  185 +
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java                                 |    9 
 opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java                    |    8 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java                         |    7 
 opendj-sdk/opends/src/server/org/opends/server/replication/common/FirstChangeNumberVirtualAttributeProvider.java            |  209 +
 opendj-sdk/opends/src/server/org/opends/server/replication/common/LastChangeNumberVirtualAttributeProvider.java             |  209 +
 opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDB.java                                            |  742 +++++
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java                                             |   24 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java                                      |   40 
 opendj-sdk/opends/resource/schema/02-config.ldif                                                                            |    9 
 51 files changed, 6,343 insertions(+), 1,427 deletions(-)

diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index 4d37f3e..20dafcd 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -2468,6 +2468,28 @@
 ds-cfg-value: 1
 ds-cfg-filter: (objectClass=ds-root-dse)
 
+dn: cn=External Changelog First Draft Change Number,cn=Virtual Attributes,cn=config
+objectClass: ds-cfg-virtual-attribute
+objectClass: ds-cfg-user-defined-virtual-attribute
+objectClass: top
+cn: External Changelog First Draft Change Number
+ds-cfg-attribute-type: firstChangeNumber
+ds-cfg-enabled: true
+ds-cfg-java-class: org.opends.server.replication.common.FirstChangeNumberVirtualAttributeProvider
+ds-cfg-value: 0
+ds-cfg-filter: (objectClass=ds-root-dse)
+
+dn: cn=External Changelog Last Draft Change Number,cn=Virtual Attributes,cn=config
+objectClass: ds-cfg-virtual-attribute
+objectClass: ds-cfg-user-defined-virtual-attribute
+objectClass: top
+cn: External Changelog Last Draft Change Number
+ds-cfg-attribute-type: lastChangeNumber
+ds-cfg-enabled: true
+ds-cfg-java-class: org.opends.server.replication.common.LastChangeNumberVirtualAttributeProvider
+ds-cfg-value: 0
+ds-cfg-filter: (objectClass=ds-root-dse)
+
 dn: cn=Work Queue,cn=config
 objectClass: top
 objectClass: ds-cfg-work-queue
diff --git a/opendj-sdk/opends/resource/schema/00-core.ldif b/opendj-sdk/opends/resource/schema/00-core.ldif
index 1ed9795..06661fd 100644
--- a/opendj-sdk/opends/resource/schema/00-core.ldif
+++ b/opendj-sdk/opends/resource/schema/00-core.ldif
@@ -156,6 +156,10 @@
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 X-ORIGIN 'RFC 4519' )
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.585 NAME 'lastExternalChangelogCookie'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.593 NAME 'firstChangeNumber'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.594 NAME 'lastChangeNumber'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'OpenDS Directory Server' )
 attributeTypes: ( 2.5.4.51 NAME 'houseIdentifier' EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768}
   X-ORIGIN 'RFC 4519' )
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index cce5b1b..979bd43 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/opends/resource/schema/02-config.ldif
@@ -2410,7 +2410,11 @@
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
   USAGE directoryOperation
   X-ORIGIN 'OpenDS Directory Server' )
-  
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.595
+  NAME 'ds-cfg-changetime-heartbeat-interval'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  SINGLE-VALUE
+  X-ORIGIN 'OpenDS Directory Server' )
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.592
   NAME 'ds-cfg-solve-conflicts'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
@@ -2976,7 +2980,8 @@
         ds-cfg-referrals-url $
         ds-cfg-fractional-exclude $
         ds-cfg-fractional-include $
-        ds-cfg-solve-conflicts )
+        ds-cfg-solve-conflicts $
+        ds-cfg-changetime-heartbeat-interval)
   X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.58
   NAME 'ds-cfg-length-based-password-validator'
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml
index 49450f0..a8439b5 100644
--- a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml
@@ -153,6 +153,30 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
+    <adm:property name="changetime-heartbeat-interval" advanced="true">
+    <adm:synopsis>
+      Specifies the heart-beat interval that the Directory Server will
+      use when sending its local change time to the Replication Server.
+    </adm:synopsis>
+    <adm:description>
+      The Directory Server sends a regular heart-beat to the Replication
+      within the specified interval. The heart-beat indicates the 
+      change time of the Directory Server to the Replication Server.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>1000ms</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:duration base-unit="ms" lower-limit="0" />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-changetime-heartbeat-interval</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
   <adm:property name="isolation-policy">
     <adm:synopsis>
       Specifies the behavior of the Directory Server if a write
diff --git a/opendj-sdk/opends/src/messages/messages/replication.properties b/opendj-sdk/opends/src/messages/messages/replication.properties
index 0dc17b9..ce9307d 100644
--- a/opendj-sdk/opends/src/messages/messages/replication.properties
+++ b/opendj-sdk/opends/src/messages/messages/replication.properties
@@ -410,3 +410,6 @@
  domain %s from server %s to all other servers of the topology is forbidden as \
  the source server has some fractional configuration : only fractional servers \
  in a replicated topology does not makes sense
+MILD_ERR_DRAFT_CHANGENUMBER_DATABASE_173=An error occurred when accessing the \
+ database of the draft change number : %s
+ 
\ No newline at end of file
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java b/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java
index 26feafc..616ec1a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java
@@ -302,6 +302,9 @@
     // Make sure that the entry matches the target filter.
     try
     {
+      TRACER.debugInfo(this + " " + entry + " +filter="
+          + filter.matchesEntry(entry));
+
       if (!filter.matchesEntry(entry))
       {
         return;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/common/FirstChangeNumberVirtualAttributeProvider.java b/opendj-sdk/opends/src/server/org/opends/server/replication/common/FirstChangeNumberVirtualAttributeProvider.java
new file mode 100644
index 0000000..a8a4320
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/common/FirstChangeNumberVirtualAttributeProvider.java
@@ -0,0 +1,209 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.common;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.server.UserDefinedVirtualAttributeCfg;
+import org.opends.server.api.VirtualAttributeProvider;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.AttributeValues;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.ConfigChangeResult;
+import org.opends.server.types.Entry;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.VirtualAttributeRule;
+import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
+
+
+
+/**
+ * This class implements a virtual attribute provider that allows administrators
+ * to define their own values that will be inserted into any entry that matches
+ * the criteria defined in the virtual attribute rule.  This can be used to
+ * provide functionality like Class of Service (CoS) in the Sun Java System
+ * Directory Server.
+ */
+public class FirstChangeNumberVirtualAttributeProvider
+       extends VirtualAttributeProvider<UserDefinedVirtualAttributeCfg>
+       implements ConfigurationChangeListener<UserDefinedVirtualAttributeCfg>
+{
+  private static final DebugTracer TRACER = getTracer();
+  // The current configuration for this virtual attribute provider.
+  private UserDefinedVirtualAttributeCfg currentConfig;
+
+  /**
+   * Creates a new instance of this member virtual attribute provider.
+   */
+  public FirstChangeNumberVirtualAttributeProvider()
+  {
+    super();
+
+    // All initialization should be performed in the
+    // initializeVirtualAttributeProvider method.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void initializeVirtualAttributeProvider(
+                            UserDefinedVirtualAttributeCfg configuration)
+         throws ConfigException, InitializationException
+  {
+    this.currentConfig = configuration;
+    configuration.addUserDefinedChangeListener(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void finalizeVirtualAttributeProvider()
+  {
+    currentConfig.removeUserDefinedChangeListener(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isMultiValued()
+  {
+    if (currentConfig == null)
+    {
+      return true;
+    }
+    else
+    {
+      return (currentConfig.getValue().size() > 1);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public Set<AttributeValue> getValues(Entry entry,VirtualAttributeRule rule)
+  {
+    Set<AttributeValue> values = new HashSet<AttributeValue>();
+    String first="0";
+    try
+    {
+      ECLWorkflowElement eclwe = (ECLWorkflowElement)
+      DirectoryServer.getWorkflowElement("EXTERNAL CHANGE LOG");
+      if (eclwe!=null)
+      {
+        first = String.valueOf(
+            eclwe.getReplicationServer().getFirstDraftChangeNumber());
+      }
+    }
+    catch(Exception e)
+    {
+
+    }
+    AttributeValue value =
+      AttributeValues.create(
+          ByteString.valueOf(first),
+          ByteString.valueOf(first));
+    values=Collections.singleton(value);
+    return values;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isSearchable(VirtualAttributeRule rule,
+                              SearchOperation searchOperation)
+  {
+    // We will not allow searches based only on user-defined virtual attributes.
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void processSearch(VirtualAttributeRule rule,
+                            SearchOperation searchOperation)
+  {
+    searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+    return;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isConfigurationChangeAcceptable(
+                      UserDefinedVirtualAttributeCfg configuration,
+                      List<Message> unacceptableReasons)
+  {
+    // The new configuration should always be acceptable.
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConfigChangeResult applyConfigurationChange(
+                                 UserDefinedVirtualAttributeCfg configuration)
+  {
+    // Just accept the new configuration as-is.
+    currentConfig = configuration;
+
+    return new ConfigChangeResult(ResultCode.SUCCESS, false);
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/common/LastChangeNumberVirtualAttributeProvider.java b/opendj-sdk/opends/src/server/org/opends/server/replication/common/LastChangeNumberVirtualAttributeProvider.java
new file mode 100644
index 0000000..a2a44b8
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/common/LastChangeNumberVirtualAttributeProvider.java
@@ -0,0 +1,209 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.common;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.server.UserDefinedVirtualAttributeCfg;
+import org.opends.server.api.VirtualAttributeProvider;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.AttributeValues;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.ConfigChangeResult;
+import org.opends.server.types.Entry;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.VirtualAttributeRule;
+import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
+
+
+
+/**
+ * This class implements a virtual attribute provider that allows administrators
+ * to define their own values that will be inserted into any entry that matches
+ * the criteria defined in the virtual attribute rule.  This can be used to
+ * provide functionality like Class of Service (CoS) in the Sun Java System
+ * Directory Server.
+ */
+public class LastChangeNumberVirtualAttributeProvider
+       extends VirtualAttributeProvider<UserDefinedVirtualAttributeCfg>
+       implements ConfigurationChangeListener<UserDefinedVirtualAttributeCfg>
+{
+  private static final DebugTracer TRACER = getTracer();
+  // The current configuration for this virtual attribute provider.
+  private UserDefinedVirtualAttributeCfg currentConfig;
+
+  /**
+   * Creates a new instance of this member virtual attribute provider.
+   */
+  public LastChangeNumberVirtualAttributeProvider()
+  {
+    super();
+
+    // All initialization should be performed in the
+    // initializeVirtualAttributeProvider method.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void initializeVirtualAttributeProvider(
+                            UserDefinedVirtualAttributeCfg configuration)
+         throws ConfigException, InitializationException
+  {
+    this.currentConfig = configuration;
+    configuration.addUserDefinedChangeListener(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void finalizeVirtualAttributeProvider()
+  {
+    currentConfig.removeUserDefinedChangeListener(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isMultiValued()
+  {
+    if (currentConfig == null)
+    {
+      return true;
+    }
+    else
+    {
+      return (currentConfig.getValue().size() > 1);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public Set<AttributeValue> getValues(Entry entry,VirtualAttributeRule rule)
+  {
+    Set<AttributeValue> values = new HashSet<AttributeValue>();
+    String last = "0";
+    try
+    {
+      ECLWorkflowElement eclwe = (ECLWorkflowElement)
+      DirectoryServer.getWorkflowElement("EXTERNAL CHANGE LOG");
+      if (eclwe!=null)
+      {
+        last = String.valueOf(
+            eclwe.getReplicationServer().getLastDraftChangeNumber());
+      }
+    }
+    catch(Exception e)
+    {
+
+    }
+    AttributeValue value =
+      AttributeValues.create(
+          ByteString.valueOf(last),
+          ByteString.valueOf(last));
+    values=Collections.singleton(value);
+    return values;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isSearchable(VirtualAttributeRule rule,
+                              SearchOperation searchOperation)
+  {
+    // We will not allow searches based only on user-defined virtual attributes.
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void processSearch(VirtualAttributeRule rule,
+                            SearchOperation searchOperation)
+  {
+    searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+    return;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isConfigurationChangeAcceptable(
+                      UserDefinedVirtualAttributeCfg configuration,
+                      List<Message> unacceptableReasons)
+  {
+    // The new configuration should always be acceptable.
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConfigChangeResult applyConfigurationChange(
+                                 UserDefinedVirtualAttributeCfg configuration)
+  {
+    // Just accept the new configuration as-is.
+    currentConfig = configuration;
+
+    return new ConfigChangeResult(ResultCode.SUCCESS, false);
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java b/opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java
index 8ae6f2f..b010c21 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java
@@ -26,9 +26,16 @@
  */
 package org.opends.server.replication.common;
 
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.TreeMap;
 
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.Severity;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+
 
 /**
  * This object is used to store a list of ServerState object, one by
@@ -106,7 +113,7 @@
    */
   public void update(String serviceId, ServerState serverState)
   {
-    list.put(serviceId,serverState);
+    list.put(serviceId,serverState.duplicate());
   }
 
   /**
@@ -208,11 +215,59 @@
     {
       ServerState state = list.get(serviceId);
       ServerState coveredState = covered.list.get(serviceId);
-      if ((coveredState == null) || (!state.cover(coveredState)))
+      if ((state==null)||(coveredState == null) || (!state.cover(coveredState)))
       {
         return false;
       }
     }
     return true;
   }
+
+  /**
+   * Splits the provided generalizedServerState being a String with the
+   * following syntax: "domain1:state1;domain2:state2;..."
+   * to a hashmap of (domain DN, domain ServerState).
+   * @param multidomainserverstate the provided state
+   * @exception DirectoryException when an error occurs
+   * @return the splited state.
+   */
+  public static HashMap<String,ServerState> splitGenStateToServerStates(
+      String multidomainserverstate)
+      throws DirectoryException
+  {
+    HashMap<String,ServerState> startStates = new HashMap<String,ServerState>();
+    try
+    {
+      // Split the provided multidomainserverstate into domains
+      String[] domains = multidomainserverstate.split(";");
+      for (String domain : domains)
+      {
+        // For each domain, split the changenumbers by server
+        // and build a server state (SHOULD BE OPTIMIZED)
+        ServerState serverStateByDomain = new ServerState();
+
+        String[] fields = domain.split(":");
+        String domainBaseDN = fields[0];
+        if (fields.length>1)
+        {
+          String strState = fields[1];
+          String[] strCN = strState.split(" ");
+          for (String sr : strCN)
+          {
+            ChangeNumber fromChangeNumber = new ChangeNumber(sr);
+            serverStateByDomain.update(fromChangeNumber);
+          }
+        }
+        startStates.put(domainBaseDN, serverStateByDomain);
+      }
+    }
+    catch(Exception e)
+    {
+      throw new DirectoryException(
+          ResultCode.OPERATIONS_ERROR,
+          Message.raw(Category.SYNC, Severity.INFORMATION,"Exception raised: "),
+          e);
+    }
+    return startStates;
+  }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java b/opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java
index bf7565b..09cb006 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java
@@ -222,7 +222,8 @@
       {
         ChangeNumber change = list.get(key);
         Date date = new Date(change.getTime());
-        set.add(change.toString() + " " + date.toString());
+        set.add(change.toString() + " " + date.toString() + " "
+            + change.getTime());
       }
     }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
index 5d8d0cc..49f6f59 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -530,7 +530,8 @@
       saveGenerationId(generationId);
     }
 
-    startPublishService(replicationServers, window, heartbeatInterval);
+    startPublishService(replicationServers, window, heartbeatInterval,
+        configuration.getChangetimeHeartbeatInterval());
 
     /*
      * ChangeNumberGenerator is used to create new unique ChangeNumbers
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java
index 2c3972a..eb9384e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java
@@ -315,22 +315,22 @@
     if (protocolVersion == ProtocolVersion.REPLICATION_PROTOCOL_V1)
     {
       return "AddMsg content: " +
-        "\nprotocolVersion: " + protocolVersion +
-        "\ndn: " + dn +
-        "\nchangeNumber: " + changeNumber +
-        "\nuniqueId: " + uniqueId +
-        "\nassuredFlag: " + assuredFlag;
+        " protocolVersion: " + protocolVersion +
+        " dn: " + dn +
+        " changeNumber: " + changeNumber +
+        " uniqueId: " + uniqueId +
+        " assuredFlag: " + assuredFlag;
     }
     if (protocolVersion >= ProtocolVersion.REPLICATION_PROTOCOL_V2)
     {
       return "AddMsg content: " +
-        "\nprotocolVersion: " + protocolVersion +
-        "\ndn: " + dn +
-        "\nchangeNumber: " + changeNumber +
-        "\nuniqueId: " + uniqueId +
-        "\nassuredFlag: " + assuredFlag +
-        "\nassuredMode: " + assuredMode +
-        "\nsafeDataLevel: " + safeDataLevel;
+        " protocolVersion: " + protocolVersion +
+        " dn: " + dn +
+        " changeNumber: " + changeNumber +
+        " uniqueId: " + uniqueId +
+        " assuredFlag: " + assuredFlag +
+        " assuredMode: " + assuredMode +
+        " safeDataLevel: " + safeDataLevel;
     }
     return "!!! Unknown version: " + protocolVersion + "!!!";
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ChangeTimeHeartbeatMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ChangeTimeHeartbeatMsg.java
new file mode 100644
index 0000000..ff1f5ee
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ChangeTimeHeartbeatMsg.java
@@ -0,0 +1,148 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.protocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.zip.DataFormatException;
+
+import org.opends.server.replication.common.ChangeNumber;
+
+/**
+ * Class that define messages sent by a replication domain (DS)
+ * to the replication server to let the RS know the DS current
+ * change time.
+ */
+public class ChangeTimeHeartbeatMsg extends ReplicationMsg
+{
+  /**
+   * The ChangeNumber containing the change time.
+   */
+  private final ChangeNumber changeNumber;
+
+  /**
+   * Constructor of a Change Time Heartbeat message.
+   */
+  public ChangeTimeHeartbeatMsg()
+  {
+    this.changeNumber = new ChangeNumber((long)0,0,(short)0);
+  }
+
+  /**
+   * Constructor of a Change Time Heartbeat message providing
+   * the change time value in a change number.
+   * @param cn The provided change number.
+   */
+  public ChangeTimeHeartbeatMsg(ChangeNumber cn)
+  {
+    this.changeNumber = cn;
+  }
+
+  /**
+   * Get a change number with the transmitted change time.
+   * @return the ChangeNumber
+   */
+  public ChangeNumber getChangeNumber()
+  {
+    return changeNumber;
+  }
+
+  /**
+   * Encode a change time message.
+   * @return The encoded message.
+   * @throws UnsupportedEncodingException When an error occurs.
+   */
+  public byte[] encode() throws UnsupportedEncodingException
+  {
+    byte[] changeNumberByte =
+      this.getChangeNumber().toString().getBytes("UTF-8");
+    int length = changeNumberByte.length;
+    byte[] encodedMsg = new byte[length];
+
+    /* Put the ChangeNumber */
+    addByteArray(changeNumberByte, encodedMsg, 0);
+
+    return encodedMsg;
+  }
+
+  /**
+   * Creates a message from a provided byte array.
+   * @param in The provided byte array.
+   * @throws DataFormatException When an error occurs.
+   */
+  public ChangeTimeHeartbeatMsg(byte[] in) throws DataFormatException
+  {
+    try
+    {
+      /* Read the changeNumber */
+      /* First byte is the type */
+      if (in[0] != MSG_TYPE_CT_HEARTBEAT)
+      {
+        throw new DataFormatException("byte[] is not a valid CT_HEARTBEAT msg");
+      }
+      int pos = 1;
+      int length = getNextLength(in, pos);
+      String changenumberStr = new String(in, pos, length, "UTF-8");
+      changeNumber = new ChangeNumber(changenumberStr);
+    }
+    catch (UnsupportedEncodingException e)
+    {
+      throw new DataFormatException("UTF-8 is not supported by this jvm.");
+    }
+    catch (IllegalArgumentException e)
+    {
+      throw new DataFormatException(e.getMessage());
+    }
+  }
+
+  /**
+   * Get a byte array from the message.
+   * @return The byte array containing the PDU of the message.
+   * @throws UnsupportedEncodingException When an error occurs.
+   */
+  public byte[] getBytes() throws UnsupportedEncodingException
+  {
+    try {
+      ByteArrayOutputStream oStream = new ByteArrayOutputStream();
+
+      /* Put the type of the operation */
+      oStream.write(MSG_TYPE_CT_HEARTBEAT);
+
+      /* Put the ChangeNumber */
+      byte[] changeNumberByte = changeNumber.toString().getBytes("UTF-8");
+      oStream.write(changeNumberByte);
+      oStream.write(0);
+
+      return oStream.toByteArray();
+    } catch (IOException e)
+    {
+      // never happens
+      return null;
+    }
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java
index 7927eee..070c05b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java
@@ -125,22 +125,22 @@
     if (protocolVersion == ProtocolVersion.REPLICATION_PROTOCOL_V1)
     {
       return "DeleteMsg content: " +
-        "\nprotocolVersion: " + protocolVersion +
-        "\ndn: " + dn +
-        "\nchangeNumber: " + changeNumber +
-        "\nuniqueId: " + uniqueId +
-        "\nassuredFlag: " + assuredFlag;
+        " protocolVersion: " + protocolVersion +
+        " dn: " + dn +
+        " changeNumber: " + changeNumber +
+        " uniqueId: " + uniqueId +
+        " assuredFlag: " + assuredFlag;
     }
     if (protocolVersion >= ProtocolVersion.REPLICATION_PROTOCOL_V2)
     {
       return "DeleteMsg content: " +
-        "\nprotocolVersion: " + protocolVersion +
-        "\ndn: " + dn +
-        "\nchangeNumber: " + changeNumber +
-        "\nuniqueId: " + uniqueId +
-        "\nassuredFlag: " + assuredFlag +
-        "\nassuredMode: " + assuredMode +
-        "\nsafeDataLevel: " + safeDataLevel;
+        " protocolVersion: " + protocolVersion +
+        " dn: " + dn +
+        " changeNumber: " + changeNumber +
+        " uniqueId: " + uniqueId +
+        " assuredFlag: " + assuredFlag +
+        " assuredMode: " + assuredMode +
+        " safeDataLevel: " + safeDataLevel;
     }
     return "!!! Unknown version: " + protocolVersion + "!!!";
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java
index 590d83d..2e1dc1d 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java
@@ -45,18 +45,23 @@
   // The value of the cookie updated with the current change
   private MultiDomainServerState cookie;
 
+  // The changenumber as specified by draft-good-ldap-changelog.
+  private int draftChangeNumber;
+
   /**
    * Creates a new message.
    * @param update    The provided update.
    * @param cookie    The provided cookie value
    * @param serviceId The provided serviceId.
+   * @param draftChangeNumber The provided draft change number.
    */
   public ECLUpdateMsg(LDAPUpdateMsg update, MultiDomainServerState cookie,
-      String serviceId)
+      String serviceId, int draftChangeNumber)
   {
     this.cookie = cookie;
     this.serviceId = serviceId;
     this.updateMsg = update;
+    this.draftChangeNumber = draftChangeNumber;
   }
 
   /**
@@ -93,6 +98,12 @@
       this.serviceId = new String(in, pos, length, "UTF-8");
       pos += length + 1;
 
+      // Decode the draft changeNumber
+      length = getNextLength(in, pos);
+      this.draftChangeNumber = Integer.valueOf(
+          new String(in, pos, length, "UTF-8"));
+      pos += length + 1;
+
       // Decode the msg
       /* Read the mods : all the remaining bytes but the terminating 0 */
       length = in.length - pos - 1;
@@ -152,9 +163,10 @@
   public String toString()
   {
     return "ECLUpdateMsg:[" +
-    "updateMsg: " + updateMsg +
-    "cookie: " + cookie +
-    "serviceId: " + serviceId + "]";
+    " updateMsg: " + updateMsg +
+    " cookie: " + cookie +
+    " draftChangeNumber: " + draftChangeNumber +
+    " serviceId: " + serviceId + "]";
   }
 
   /**
@@ -165,10 +177,13 @@
   {
     byte[] byteCookie    = String.valueOf(cookie).getBytes("UTF-8");
     byte[] byteServiceId = String.valueOf(serviceId).getBytes("UTF-8");
+    byte[] byteDraftChangeNumber =
+      Integer.toString(draftChangeNumber).getBytes("UTF-8");
     byte[] byteUpdateMsg = updateMsg.getBytes();
 
     int length = 1 + byteCookie.length +
                  1 + byteServiceId.length +
+                 1 + byteDraftChangeNumber.length +
                  1 + byteUpdateMsg.length + 1;
 
     byte[] resultByteArray = new byte[length];
@@ -183,9 +198,31 @@
     // Encode serviceid
     pos = addByteArray(byteServiceId, resultByteArray, pos);
 
+    /* Put the draftChangeNumber */
+    pos = addByteArray(byteDraftChangeNumber, resultByteArray, pos);
+
     // Encode msg
     pos = addByteArray(byteUpdateMsg, resultByteArray, pos);
 
     return resultByteArray;
   }
+
+  /**
+   * Setter for the draftChangeNumber of this change.
+   * @param draftChangeNumber the provided draftChangeNumber for this change.
+   */
+  public void setDraftChangeNumber(int draftChangeNumber)
+  {
+    this.draftChangeNumber = draftChangeNumber;
+  }
+
+  /**
+   * Getter for the draftChangeNumber of this change.
+   * @return the draftChangeNumber of this change.
+   */
+  public int getDraftChangeNumber()
+  {
+    return this.draftChangeNumber;
+  }
+
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java
index 96b0f6a..a60b658 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java
@@ -74,8 +74,8 @@
    * Create a heartbeat monitor thread.
    * @param threadName The name of the heartbeat thread.
    * @param session The session on which heartbeats are to be monitored.
-   * @param heartbeatInterval The expected interval between heartbeats in
-   * milliseconds.
+   * @param heartbeatInterval The expected interval between heartbeats received
+   * (in milliseconds).
    */
   public HeartbeatMonitor(String threadName, ProtocolSession session,
                           long heartbeatInterval)
@@ -93,7 +93,6 @@
     shutdown = true;
   }
 
-
   /**
    * {@inheritDoc}
    */
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java
index 5ef9135..0feb441 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java
@@ -70,9 +70,12 @@
   static final byte MSG_TYPE_START_SESSION = 27;
   static final byte MSG_TYPE_CHANGE_STATUS = 28;
   static final byte MSG_TYPE_GENERIC_UPDATE = 29;
+
+  // Protocol version : 3
   static final byte MSG_TYPE_START_ECL = 30;
   static final byte MSG_TYPE_START_ECL_SESSION = 31;
   static final byte MSG_TYPE_ECL_UPDATE = 32;
+  static final byte MSG_TYPE_CT_HEARTBEAT = 33;
 
   // Adding a new type of message here probably requires to
   // change accordingly generateMsg method below
@@ -232,6 +235,9 @@
       case MSG_TYPE_ECL_UPDATE:
         msg = new ECLUpdateMsg(buffer);
       break;
+      case MSG_TYPE_CT_HEARTBEAT:
+        msg = new ChangeTimeHeartbeatMsg(buffer);
+      break;
       default:
         throw new DataFormatException("received message with unknown type");
     }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java
index b0bedc8..16f3603 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java
@@ -101,7 +101,7 @@
   private ChangeNumber changeNumber;
 
   // Specifies whether the search is persistent and changesOnly
-  private short  isPersistent;
+  private short  isPersistent = NON_PERSISTENT;
 
   // A string helping debuging and tracing the client operation related when
   // processing, on the RS side, a request on the ECL.
@@ -203,7 +203,14 @@
    */
   public StartECLSessionMsg()
   {
+    eclRequestType = REQUEST_TYPE_FROM_COOKIE;
+    crossDomainServerState = "";
+    firstDraftChangeNumber = -1;
+    lastDraftChangeNumber = -1;
     changeNumber = new ChangeNumber((short)0,0,(short)0);
+    isPersistent = NON_PERSISTENT;
+    operationId = "-1";
+    excludedServiceIDs = new ArrayList<String>();
   }
 
   /**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/DbHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DbHandler.java
index 960cd6f..4ed5700 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/DbHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DbHandler.java
@@ -28,6 +28,7 @@
 import org.opends.messages.MessageBuilder;
 
 import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
 import static org.opends.messages.ReplicationMessages.*;
 import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
 
@@ -46,6 +47,7 @@
 import org.opends.server.types.InitializationException;
 import org.opends.server.util.TimeThread;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.common.ChangeNumber;
 import org.opends.server.replication.protocol.UpdateMsg;
 import org.opends.server.replication.server.ReplicationDB.ReplServerDBCursor;
@@ -121,6 +123,7 @@
    *
    */
   private long trimage;
+  private static final DebugTracer TRACER = getTracer();
 
   /**
    * Creates a new dbHandler associated to a given LDAP server.
@@ -291,13 +294,14 @@
     ChangeNumber recentChangeNumber = null;
 
     if (changeNumber == null)
+    {
       flush();
-
+    }
     synchronized (msgQueue)
     {
       try
       {
-        UpdateMsg msg = msgQueue.getFirst();
+        UpdateMsg msg = msgQueue.getLast();
         recentChangeNumber = msg.getChangeNumber();
       }
       catch (NoSuchElementException e)
@@ -654,4 +658,14 @@
       lastChange = db.readLastChange();
     }
   }
+
+  /**
+   * Getter fot the serverID of the server for which this database is managed.
+   *
+   * @return the serverId.
+   */
+  public short getServerId()
+  {
+    return this.serverId;
+  }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDB.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDB.java
new file mode 100644
index 0000000..15026ef
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDB.java
@@ -0,0 +1,742 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+
+import com.sleepycat.je.Cursor;
+import com.sleepycat.je.Database;
+import com.sleepycat.je.DatabaseEntry;
+import com.sleepycat.je.DatabaseException;
+import com.sleepycat.je.DeadlockException;
+import com.sleepycat.je.LockMode;
+import com.sleepycat.je.OperationStatus;
+import com.sleepycat.je.Transaction;
+
+/**
+ * This class implements the interface between the underlying database
+ * and the dbHandler class.
+ * This is the only class that should have code using the BDB interfaces.
+ */
+public class DraftCNDB
+{
+  private static final DebugTracer TRACER = getTracer();
+  private Database db = null;
+  private ReplicationDbEnv dbenv = null;
+  private ReplicationServer replicationServer;
+  private DN baseDn;
+
+  // The maximum number of retries in case of DatabaseDeadlock Exception.
+  private static final int DEADLOCK_RETRIES = 10;
+
+  // The lock used to provide exclusive access to the thread that
+  // close the db (shutdown or clear).
+  private ReentrantReadWriteLock dbCloseLock;
+
+  /**
+   * Creates a new database or open existing database that will be used
+   * to store and retrieve changes from an LDAP server.
+   * @param replicationServer The ReplicationServer that needs to be shutdown.
+   * @param dbenv The Db environment to use to create the db.
+   * @throws DatabaseException If a database problem happened.
+   */
+  public DraftCNDB(
+      ReplicationServer replicationServer,
+      ReplicationDbEnv dbenv)
+  throws DatabaseException
+  {
+    this.dbenv = dbenv;
+    this.replicationServer = replicationServer;
+
+    // Get or create the associated ReplicationServerDomain and Db.
+    db = dbenv.getOrCreateDraftCNDb();
+
+    dbCloseLock = new ReentrantReadWriteLock(true);
+  }
+
+  /**
+   * Add an entry to the database.
+   * @param draftCN      the provided draftCN.
+   *
+   * @param value        the provided value to be stored associated
+   *                     with this draftCN.
+   * @param domainBaseDN the provided domainBaseDn to be stored associated
+   *                     with this draftCN.
+   * @param changeNumber the provided replication change number to be
+   *                     stored associated with this draftCN.
+   */
+  public void addEntry(int draftCN, String value, String domainBaseDN,
+      ChangeNumber changeNumber)
+  {
+    Transaction txn = null;
+    try
+    {
+      int tries = 0;
+      boolean done = false;
+
+      // The database can return a Deadlock Exception if several threads are
+      // accessing the database at the same time. This Exception is a
+      // transient state, when it happens the transaction is aborted and
+      // the operation is attempted again up to DEADLOCK_RETRIES times.
+      while ((tries++ < DEADLOCK_RETRIES) && (!done))
+      {
+        dbCloseLock.readLock().lock();
+        try
+        {
+          txn = dbenv.beginTransaction();
+
+          DatabaseEntry key = new ReplicationDraftCNKey(draftCN);
+          DatabaseEntry data = new DraftCNData(draftCN,
+              value, domainBaseDN, changeNumber);
+          db.put(txn, key, data);
+          txn.commitWriteNoSync();
+          txn = null;
+          done = true;
+        }
+        catch (DeadlockException e)
+        {
+          if (txn != null)
+            txn.abort();
+          txn = null;
+        }
+        finally
+        {
+          dbCloseLock.readLock().unlock();
+        }
+      }
+      if (!done)
+      {
+        // Could not write to the DB after DEADLOCK_RETRIES tries.
+        // This ReplicationServer is not reliable and will be shutdown.
+        MessageBuilder mb = new MessageBuilder();
+        mb.append(ERR_CHANGELOG_SHUTDOWN_DATABASE_ERROR.get());
+        logError(mb.toMessage());
+        if (txn != null)
+        {
+          txn.abort();
+        }
+        replicationServer.shutdown();
+      }
+    }
+    catch (DatabaseException e)
+    {
+      MessageBuilder mb = new MessageBuilder();
+      mb.append(ERR_CHANGELOG_SHUTDOWN_DATABASE_ERROR.get());
+      mb.append(stackTraceToSingleLineString(e));
+      logError(mb.toMessage());
+      if (txn != null)
+      {
+        try
+        {
+          txn.abort();
+        } catch (DatabaseException e1)
+        {
+          // can't do much more. The ReplicationServer is shuting down.
+        }
+      }
+      replicationServer.shutdown();
+    }
+    catch (UnsupportedEncodingException e)
+    {
+      MessageBuilder mb = new MessageBuilder();
+      mb.append(ERR_CHANGELOG_UNSUPPORTED_UTF8_ENCODING.get());
+      mb.append(stackTraceToSingleLineString(e));
+      logError(mb.toMessage());
+      replicationServer.shutdown();
+      if (txn != null)
+      {
+        try
+        {
+          txn.abort();
+        } catch (DatabaseException e1)
+        {
+          // can't do much more. The ReplicationServer is shuting down.
+        }
+      }
+      replicationServer.shutdown();
+    }
+  }
+
+  /**
+   * Shutdown the database.
+   */
+  public void shutdown()
+  {
+    try
+    {
+      dbCloseLock.writeLock().lock();
+      try
+      {
+        db.close();
+      }
+      finally
+      {
+        dbCloseLock.writeLock().unlock();
+      }
+    }
+    catch (DatabaseException e)
+    {
+      MessageBuilder mb = new MessageBuilder();
+      mb.append(NOTE_EXCEPTION_CLOSING_DATABASE.get(this.toString()));
+      mb.append(stackTraceToSingleLineString(e));
+      logError(mb.toMessage());
+    }
+  }
+
+  /**
+   * Create a cursor that can be used to search or iterate on this DB.
+   *
+   * @param draftCN The draftCN from which the cursor must start.
+   * @throws DatabaseException If a database error prevented the cursor
+   *                           creation.
+   * @throws Exception if the ReplServerDBCursor creation failed.
+   * @return The ReplServerDBCursor.
+   */
+  public DraftCNDBCursor openReadCursor(int draftCN)
+  throws DatabaseException, Exception
+  {
+    return new DraftCNDBCursor(draftCN);
+  }
+
+  /**
+   * Create a cursor that can be used to delete some record from this
+   * ReplicationServer database.
+   *
+   * @throws DatabaseException If a database error prevented the cursor
+   *                           creation.
+   * @throws Exception if the ReplServerDBCursor creation failed.
+   *
+   * @return The ReplServerDBCursor.
+   */
+  public DraftCNDBCursor openDeleteCursor()
+  throws DatabaseException, Exception
+  {
+    return new DraftCNDBCursor();
+  }
+
+  private void closeLockedCursor(Cursor cursor)
+  throws DatabaseException
+  {
+    try
+    {
+      if (cursor != null)
+        cursor.close();
+    }
+    finally
+    {
+      dbCloseLock.readLock().unlock();
+    }
+  }
+
+  /**
+   * Read the first Change from the database, 0 when none.
+   * @return the first ChangeNumber.
+   */
+  public int readFirstSeqnum()
+  {
+    Cursor cursor = null;
+    String str = null;
+
+    try
+    {
+      dbCloseLock.readLock().lock();
+      cursor = db.openCursor(null, null);
+    }
+    catch (DatabaseException e1)
+    {
+      dbCloseLock.readLock().unlock();
+      return 0;
+    }
+    try
+    {
+      try
+      {
+        DatabaseEntry key = new DatabaseEntry();
+        DatabaseEntry entry = new DatabaseEntry();
+        OperationStatus status = cursor.getFirst(key, entry, LockMode.DEFAULT);
+        if (status != OperationStatus.SUCCESS)
+        {
+          /* database is empty */
+          return 0;
+        }
+        try
+        {
+          str = new String(key.getData(), "UTF-8");
+        } catch (UnsupportedEncodingException e)
+        {
+          // never happens
+        }
+        int sn = new Integer(str);
+        return sn;
+      }
+      finally
+      {
+        closeLockedCursor(cursor);
+      }
+    }
+    catch (DatabaseException e)
+    {
+      /* database is faulty */
+      MessageBuilder mb = new MessageBuilder();
+      mb.append(ERR_CHANGELOG_SHUTDOWN_DATABASE_ERROR.get());
+      mb.append(stackTraceToSingleLineString(e));
+      logError(mb.toMessage());
+      replicationServer.shutdown();
+      return 0;
+    }
+    catch (Exception e)
+    {
+      MessageBuilder mb = new MessageBuilder();
+      mb.append(ERR_CHANGELOG_SHUTDOWN_DATABASE_ERROR.get());
+      mb.append(stackTraceToSingleLineString(e));
+      logError(mb.toMessage());
+      replicationServer.shutdown();
+      return 0;
+    }
+  }
+
+  /**
+   * Return the record count.
+   * @return the record count.
+   */
+  public long count()
+  {
+    try
+    {
+      return db.count();
+    }
+    catch(Exception e)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, e);
+    }
+    return 0L;
+  }
+
+  /**
+   * Read the last draftCN from the database.
+   * @return the last draftCN.
+   */
+  public int readLastDraftCN()
+  {
+    Cursor cursor = null;
+    String str = null;
+
+    try
+    {
+      dbCloseLock.readLock().lock();
+      try
+      {
+        cursor = db.openCursor(null, null);
+        DatabaseEntry key = new DatabaseEntry();
+        DatabaseEntry entry = new DatabaseEntry();
+        OperationStatus status = cursor.getLast(key, entry, LockMode.DEFAULT);
+        if (status != OperationStatus.SUCCESS)
+        {
+          /* database is empty */
+          return 0;
+        }
+        try
+        {
+          str = new String(key.getData(), "UTF-8");
+        } catch (UnsupportedEncodingException e)
+        {
+          // never happens
+        }
+        int sn = new Integer(str);
+        return sn;
+      }
+      finally
+      {
+        closeLockedCursor(cursor);
+      }
+    }
+    catch (DatabaseException e)
+    {
+      MessageBuilder mb = new MessageBuilder();
+      mb.append(ERR_CHANGELOG_SHUTDOWN_DATABASE_ERROR.get());
+      mb.append(stackTraceToSingleLineString(e));
+      logError(mb.toMessage());
+      replicationServer.shutdown();
+      return 0;
+    }
+    catch (Exception e)
+    {
+      replicationServer.shutdown();
+      return 0;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return "DraftCNDB:" + baseDn.toString();
+  }
+
+  /**
+   * This Class implements a cursor that can be used to browse the database.
+   */
+  public class DraftCNDBCursor
+  {
+    private Cursor cursor = null;
+
+    // The transaction that will protect the actions done with the cursor
+    // Will be let null for a read cursor
+    // Will be set non null for a write cursor
+    private Transaction txn = null;
+    DatabaseEntry key = new DatabaseEntry();
+    DatabaseEntry entry = new DatabaseEntry();
+
+    /**
+     * Creates a cursor that can be used for browsing the db.
+     *
+     * @param startingDraftCN the draftCN from which the cursor must
+     *                        start.
+     * @throws Exception      when the startingDraftCN does not exist.
+     */
+    private DraftCNDBCursor(int startingDraftCN) throws Exception
+    {
+      try
+      {
+        // Take the lock. From now on, whatever error that happen in the life
+        // of this cursor should end by unlocking that lock. We must also
+        // unlock it when throwing an exception.
+        dbCloseLock.readLock().lock();
+
+        cursor = db.openCursor(txn, null);
+        if (startingDraftCN >= 0)
+        {
+          key = new ReplicationDraftCNKey(startingDraftCN);
+          entry = new DatabaseEntry();
+
+          if (cursor.getSearchKey(key, entry, LockMode.DEFAULT) !=
+            OperationStatus.SUCCESS)
+          {
+            // We could not move the cursor to the expected startingChangeNumber
+            if (cursor.getSearchKeyRange(key, entry, LockMode.DEFAULT) !=
+              OperationStatus.SUCCESS)
+            {
+              // We could not even move the cursor closed to it => failure
+              throw new Exception("ChangeLog Draft Change Number " +
+                  startingDraftCN + " is not available");
+            }
+            else
+            {
+              // We can move close to the startingChangeNumber.
+              // Let's create a cursor from that point.
+              DatabaseEntry key = new DatabaseEntry();
+              DatabaseEntry data = new DatabaseEntry();
+              if (cursor.getPrev(key, data, LockMode.DEFAULT) !=
+                OperationStatus.SUCCESS)
+              {
+                closeLockedCursor(cursor);
+                dbCloseLock.readLock().lock();
+                cursor = db.openCursor(txn, null);
+              }
+            }
+          }
+          else
+          {
+            // success : key has the right value
+          }
+        }
+      }
+      catch (Exception e)
+      {
+        // Unlocking is required before throwing any exception
+        closeLockedCursor(cursor);
+        throw (e);
+      }
+    }
+
+    private DraftCNDBCursor() throws DatabaseException
+    {
+      try
+      {
+        // We'll go on only if no close or no clear is running
+        dbCloseLock.readLock().lock();
+
+        // Create the transaction that will protect whatever done with this
+        // write cursor.
+        txn = dbenv.beginTransaction();
+
+        cursor = db.openCursor(txn, null);
+      }
+      catch(DatabaseException e)
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+
+        if (txn != null)
+        {
+          try
+          {
+            txn.abort();
+          }
+          catch (DatabaseException dbe)
+          {}
+        }
+        closeLockedCursor(cursor);
+        throw (e);
+      }
+    }
+
+    /**
+     * Close the ReplicationServer Cursor.
+     */
+    public void close()
+    {
+      try
+      {
+        closeLockedCursor(cursor);
+        cursor = null;
+      }
+      catch (DatabaseException e)
+      {
+        MessageBuilder mb = new MessageBuilder();
+        mb.append(ERR_CHANGELOG_SHUTDOWN_DATABASE_ERROR.get());
+        mb.append(stackTraceToSingleLineString(e));
+        logError(mb.toMessage());
+        replicationServer.shutdown();
+      }
+      if (txn != null)
+      {
+        try
+        {
+          txn.commit();
+        } catch (DatabaseException e)
+        {
+          MessageBuilder mb = new MessageBuilder();
+          mb.append(ERR_CHANGELOG_SHUTDOWN_DATABASE_ERROR.get());
+          mb.append(stackTraceToSingleLineString(e));
+          logError(mb.toMessage());
+          replicationServer.shutdown();
+        }
+      }
+    }
+
+    /**
+     * Abort the Cursor after a Deadlock Exception.
+     * This method catch and ignore the DeadlockException because
+     * this must be done when aborting a cursor after a DeadlockException
+     * (per the Cursor documentation).
+     * This should not be used in any other case.
+     */
+    public void abort()
+    {
+      if (cursor == null)
+        return;
+      try
+      {
+        closeLockedCursor(cursor);
+        cursor = null;
+      }
+      catch (DeadlockException e1)
+      {
+        // The DB documentation states that a DeadlockException
+        // on the close method of a cursor that is aborting should
+        // be ignored.
+      }
+      catch (DatabaseException e)
+      {
+        MessageBuilder mb = new MessageBuilder();
+        mb.append(ERR_CHANGELOG_SHUTDOWN_DATABASE_ERROR.get());
+        mb.append(stackTraceToSingleLineString(e));
+        logError(mb.toMessage());
+        replicationServer.shutdown();
+      }
+      if (txn != null)
+      {
+        try
+        {
+          txn.abort();
+        } catch (DatabaseException e)
+        {
+          MessageBuilder mb = new MessageBuilder();
+          mb.append(ERR_CHANGELOG_SHUTDOWN_DATABASE_ERROR.get());
+          mb.append(stackTraceToSingleLineString(e));
+          logError(mb.toMessage());
+          replicationServer.shutdown();
+        }
+      }
+    }
+
+    /**
+     * Getter for the value field of the current cursor.
+     * @return The current value field.
+     * @throws DatabaseException When an error happens.
+     */
+    public String currentValue() throws DatabaseException
+    {
+      try
+      {
+        OperationStatus status =
+          cursor.getCurrent(key, entry, LockMode.DEFAULT);
+
+        if (status != OperationStatus.SUCCESS)
+        {
+          return null;
+        }
+        DraftCNData seqnumData = new DraftCNData(entry.getData());
+        return seqnumData.getValue();
+      }
+      catch(Exception e)
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+      return null;
+    }
+
+    /**
+     * Getter for the serviceID field of the current cursor.
+     * @return The current serviceID.
+     * @throws DatabaseException When an error happens.
+     */
+    public String currentServiceID() throws DatabaseException
+    {
+      try
+      {
+        OperationStatus status =
+          cursor.getCurrent(key, entry, LockMode.DEFAULT);
+
+        if (status != OperationStatus.SUCCESS)
+        {
+          return null;
+        }
+        DraftCNData seqnumData = new DraftCNData(entry.getData());
+        return seqnumData.getServiceID();
+      }
+      catch(Exception e)
+      {
+
+      }
+      return null;
+    }
+
+    /**
+     * Returns the replication changeNumber associated with the current key.
+     * @return the replication changeNumber
+     * @throws DatabaseException when a problem occurs.
+     */
+    public ChangeNumber currentChangeNumber() throws DatabaseException
+    {
+      try
+      {
+        OperationStatus status =
+          cursor.getCurrent(key, entry, LockMode.DEFAULT);
+
+        if (status != OperationStatus.SUCCESS)
+        {
+          return null;
+        }
+        DraftCNData seqnumData =
+          new DraftCNData(entry.getData());
+        return seqnumData.getChangeNumber();
+      }
+      catch(Exception e)
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+      return null;
+    }
+
+    /**
+     * a.
+     * @return a.
+     * @throws DatabaseException a.
+     */
+    public boolean next() throws DatabaseException
+    {
+      OperationStatus status = cursor.getNext(key, entry, LockMode.DEFAULT);
+      if (status != OperationStatus.SUCCESS)
+      {
+        return false;
+      }
+      return true;
+    }
+
+    /**
+     * Delete the record at the current cursor position.
+     *
+     * @throws DatabaseException In case of database problem.
+     */
+    public void delete() throws DatabaseException
+    {
+      cursor.delete();
+    }
+  }
+
+  /**
+   * Clears this change DB from the changes it contains.
+   *
+   * @throws Exception Throws an exception it occurs.
+   * @throws DatabaseException Throws a DatabaseException when it occurs.
+   */
+  public void clear() throws Exception, DatabaseException
+  {
+    // The coming users will be blocked until the clear is done
+    dbCloseLock.writeLock().lock();
+    try
+    {
+      String dbName = db.getDatabaseName();
+
+      // Closing is requested by the Berkeley DB before truncate
+      db.close();
+
+      // Clears the changes
+      dbenv.clearDb(dbName);
+
+      // RE-create the db
+      db = dbenv.getOrCreateDraftCNDb();
+    }
+    catch(Exception e)
+    {
+      MessageBuilder mb = new MessageBuilder();
+      mb.append(ERR_ERROR_CLEARING_DB.get(this.toString(),
+          e.getMessage() + " " +
+          stackTraceToSingleLineString(e)));
+      logError(mb.toMessage());
+    }
+    finally
+    {
+      // Relax the waiting users
+      dbCloseLock.writeLock().unlock();
+    }
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNData.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNData.java
new file mode 100644
index 0000000..5831952
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNData.java
@@ -0,0 +1,173 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+
+import java.io.UnsupportedEncodingException;
+
+import org.opends.messages.Message;
+import org.opends.server.replication.common.ChangeNumber;
+
+import com.sleepycat.je.DatabaseEntry;
+
+/**
+ * SuperClass of DatabaseEntry used for data stored in the DraftCNDB.
+ */
+public class DraftCNData extends DatabaseEntry
+{
+  private static final String FIELD_SEPARATOR = "!";
+
+  String value;
+  String serviceID;
+  ChangeNumber changeNumber;
+
+  /**
+   * Creates a record to be stored in the DraftCNDB.
+   * @param draftCN The DraftCN key.
+   * @param value The value (cookie).
+   * @param serviceID The serviceID (domain DN).
+   * @param changeNumber The replication change number.
+   *
+   * @throws UnsupportedEncodingException When the encoding of the message
+   *         failed because the UTF-8 encoding is not supported.
+   */
+  public DraftCNData(int draftCN, String value,
+      String serviceID, ChangeNumber changeNumber)
+  throws UnsupportedEncodingException
+  {
+    String record = value
+                   + FIELD_SEPARATOR + serviceID
+                   + FIELD_SEPARATOR + changeNumber;
+
+    byte[] byteValue;
+    try
+    {
+      byteValue = record.getBytes("UTF-8");
+      this.setData(byteValue);
+    }
+    catch (UnsupportedEncodingException e)
+    {
+      // can't happen
+      return;
+    }
+  }
+
+  /**
+   * Creates a record to be stored in the DraftCNDB from the provided byte[].
+   * @param data the provided byte[].
+   * @throws Exception a.
+   */
+  public DraftCNData(byte[] data) throws Exception
+  {
+    decodeData(data);
+  }
+
+  /**
+   * Decode a record into fields.
+   * @param data the provided byte array.
+   * @throws Exception when a problem occurs.
+   */
+  public void decodeData(byte[] data)
+  throws Exception
+  {
+    try
+    {
+      String stringData = new String(data, "UTF-8");
+
+      String[] str = stringData.split(FIELD_SEPARATOR, 3);
+      value = str[0];
+      serviceID = str[1];
+      changeNumber = new ChangeNumber(str[2]);
+    }
+    catch (UnsupportedEncodingException e)
+    {
+      // should never happens
+      // TODO: i18n
+      throw new ReplicationDBException(Message.raw("need UTF-8 support"));
+    }
+  }
+
+  /**
+   * Getter for the value.
+   * @return the value.
+   * @throws Exception when a problem occurs.
+   */
+  public String getValue()
+  throws Exception
+  {
+    if (value == null)
+      this.decodeData(this.getData());
+    return this.value;
+  }
+
+  /**
+   * Getter for the service ID.
+   * @return The serviceID..
+   * @throws Exception when a problem occurs.
+   */
+  public String getServiceID()
+  throws Exception
+  {
+    if (value == null)
+      this.decodeData(this.getData());
+    return this.serviceID;
+  }
+
+  /**
+   * Getter for the replication change number.
+   * @return the replication change number.
+   * @throws Exception when a problem occurs.
+   */
+  public ChangeNumber getChangeNumber()
+  throws Exception
+  {
+    if (value == null)
+      this.decodeData(this.getData());
+    return this.changeNumber;
+  }
+
+  /**
+   * Provide a string representation of these data.
+   * @return the string representation of these data.
+   */
+  public String toString()
+  {
+    StringBuilder buffer = new StringBuilder();
+    toString(buffer);
+    return buffer.toString();
+  }
+
+  /**
+   * Dump a string representation of these data into the provided buffer.
+   * @param buffer the provided buffer.
+   */
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("DraftCNData : [value=" + value);
+    buffer.append("] [serviceID=" + serviceID);
+    buffer.append("] [changeNumber=" + changeNumber + "]");
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDbHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDbHandler.java
new file mode 100644
index 0000000..f884b31
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDbHandler.java
@@ -0,0 +1,548 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.server.admin.std.server.MonitorProviderCfg;
+import org.opends.server.api.DirectoryThread;
+import org.opends.server.api.MonitorProvider;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.server.DraftCNDB.*;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.InitializationException;
+
+import com.sleepycat.je.DatabaseException;
+import com.sleepycat.je.DeadlockException;
+
+/**
+ * This class is used for managing the replicationServer database for each
+ * server in the topology.
+ * It is responsible for efficiently saving the updates that is received from
+ * each master server into stable storage.
+ * This class is also able to generate a ReplicationIterator that can be
+ * used to read all changes from a given ChangeNUmber.
+ *
+ * This class publish some monitoring information below cn=monitor.
+ *
+ */
+public class DraftCNDbHandler implements Runnable
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+  // A dedicated thread loops trim().
+  // trim()  : deletes from the DB a number of changes that are older than a
+  //           certain date.
+  //
+  static int NO_KEY = 0;
+
+  private DraftCNDB db;
+  private int firstkey = NO_KEY;
+  private int lastkey = NO_KEY;
+  private DbMonitorProvider dbMonitor = new DbMonitorProvider();
+  private boolean shutdown = false;
+  private boolean trimDone = false;
+  private DirectoryThread thread = null;
+  private final Object flushLock = new Object();
+  private ReplicationServer replicationServer;
+
+  // The maximum number of retries in case of DatabaseDeadlock Exception.
+  private static final int DEADLOCK_RETRIES = 10;
+
+  /**
+   *
+   * The trim age in milliseconds. Changes record in the change DB that
+   * are older than this age are removed.
+   *
+   */
+  private long trimage;
+
+  /**
+   * Creates a new dbHandler associated to a given LDAP server.
+   *
+   * @param replicationServer The ReplicationServer that creates this dbHandler.
+   * @param dbenv the Database Env to use to create the ReplicationServer DB.
+   * server for this domain.
+   * @throws DatabaseException If a database problem happened
+   */
+  public DraftCNDbHandler(ReplicationServer replicationServer,
+      ReplicationDbEnv dbenv)
+         throws DatabaseException
+  {
+    this.replicationServer = replicationServer;
+    this.trimage = replicationServer.getTrimage();
+
+    // DB initialization
+    db = new DraftCNDB(replicationServer, dbenv);
+    firstkey = db.readFirstSeqnum();
+    lastkey = db.readLastDraftCN();
+
+    // Triming thread
+    thread = new DirectoryThread(this, "Replication DraftCN db ");
+    thread.start();
+
+    // Monitoring registration
+    DirectoryServer.deregisterMonitorProvider(
+                      dbMonitor.getMonitorInstanceName());
+    DirectoryServer.registerMonitorProvider(dbMonitor);
+  }
+
+  /**
+   * Add an update to the list of messages that must be saved to the db
+   * managed by this db handler.
+   * This method is blocking if the size of the list of message is larger
+   * than its maximum.
+   * @param key The key for this record in the db.
+   * @param value The associated value.
+   * @param serviceID The associated serviceID.
+   * @param cn The associated replication change number.
+   *
+   */
+  public synchronized void add(int key, String value, String serviceID,
+      ChangeNumber cn)
+  {
+    db.addEntry(key, value, serviceID, cn);
+
+    if (debugEnabled())
+      TRACER.debugInfo("In DraftCNDbhandler.add, added: "
+        + " key=" + key
+        + " value=" + value
+        + " serviceID=" + serviceID
+        + " cn=" + cn);
+  }
+
+  /**
+   * Get the firstChange.
+   * @return Returns the firstChange.
+   */
+  public int getFirstKey()
+  {
+    return db.readFirstSeqnum();
+  }
+
+  /**
+   * Get the lastChange.
+   * @return Returns the lastChange.
+   */
+  public int getLastKey()
+  {
+    return db.readLastDraftCN();
+  }
+
+  /**
+   * Get the number of changes.
+   * @return Returns the number of changes.
+   */
+  public long count()
+  {
+    return db.count();
+  }
+
+  /**
+   * Get a read cursor on the database from a provided key.
+   * The cursor MUST be released after use.
+   * @param key The provided key.
+   * @return the new cursor.
+   */
+  public DraftCNDBCursor getReadCursor(int key)
+  {
+    try
+    {
+      return db.openReadCursor(key);
+    }
+    catch(Exception e)
+    {
+      return null;
+    }
+  }
+
+  /**
+   * Release a provided read cursor.
+   * @param cursor The provided read cursor.
+   */
+  public void releaseReadCursor(DraftCNDBCursor cursor)
+  {
+    try
+    {
+      cursor.close();
+    }
+    catch(Exception e)
+    {
+    }
+  }
+
+  /**
+   * Generate a new ReplicationIterator that allows to browse the db
+   * managed by this dbHandler and starting at the position defined
+   * by a given changeNumber.
+   *
+   * @param  startDraftCN The position where the iterator must start.
+   *
+   * @return a new ReplicationIterator that allows to browse the db
+   *         managed by this dbHandler and starting at the position defined
+   *         by a given changeNumber.
+   *
+   * @throws DatabaseException if a database problem happened.
+   * @throws Exception  If there is no other change to push after change
+   *         with changeNumber number.
+   */
+  public DraftCNDbIterator generateIterator(int startDraftCN)
+                           throws DatabaseException, Exception
+  {
+    DraftCNDbIterator it =
+      new DraftCNDbIterator(db, startDraftCN);
+    return it;
+  }
+
+  /**
+   * Shutdown this dbHandler.
+   */
+  public void shutdown()
+  {
+    if (shutdown == true)
+    {
+      return;
+    }
+
+    shutdown  = true;
+    synchronized (this)
+    {
+      this.notifyAll();
+    }
+
+    synchronized (this)
+    {
+      while (trimDone  == false)
+      {
+        try
+        {
+          this.wait();
+        } catch (Exception e)
+        {}
+      }
+    }
+
+    db.shutdown();
+    DirectoryServer.deregisterMonitorProvider(
+        dbMonitor.getMonitorInstanceName());
+  }
+
+  /**
+   * Run method for this class.
+   * Periodically Flushes the ReplicationServerDomain cache from memory to the
+   * stable storage and trims the old updates.
+   */
+  public void run()
+  {
+    while (shutdown == false)
+    {
+      try {
+        trim();
+
+        synchronized (this)
+        {
+          try
+          {
+            this.wait(1000);
+          } catch (InterruptedException e)
+          { }
+        }
+      } catch (Exception end)
+      {
+        MessageBuilder mb = new MessageBuilder();
+        mb.append(ERR_EXCEPTION_CHANGELOG_TRIM_FLUSH.get());
+        mb.append(stackTraceToSingleLineString(end));
+        logError(mb.toMessage());
+        if (replicationServer != null)
+          replicationServer.shutdown();
+        break;
+      }
+    }
+
+    synchronized (this)
+    {
+      trimDone = true;
+      this.notifyAll();
+    }
+  }
+
+  /**
+   * Trim old changes from this database.
+   * @throws DatabaseException In case of database problem.
+   * @throws Exception In case of database problem.
+   */
+  public void trim() throws DatabaseException, Exception
+  {
+    if (trimage == 0)
+      return;
+
+    if (this.count()==0)
+      return;
+
+    int tries = 0;
+    boolean done = false;
+    DraftCNDBCursor cursor = db.openDeleteCursor();
+    try
+    {
+      // In case of deadlock detection by the Database, this thread can
+      // by aborted by a DeadlockException. This is a transient error and
+      // the transaction should be attempted again.
+      // We will try DEADLOCK_RETRIES times before failing.
+      while ((tries++ < DEADLOCK_RETRIES) && (!done))
+      {
+        // let's traverse the DraftCNDb
+        if (!cursor.next())
+          break;
+
+        // Fomr the draftCNDb change record, get the domain and changeNumber
+        String serviceID = cursor.currentServiceID();
+        ChangeNumber cn = cursor.currentChangeNumber();
+        ReplicationServerDomain domain =
+          replicationServer.getReplicationServerDomain(serviceID, false);
+
+        if (domain==null)
+        {
+          // the domain has been removed since the record was written in the
+          // draftCNDb, thus it makes no sense to keep the record in the
+          // draftCNDb.
+          cursor.delete();
+        }
+        else
+        {
+          // let's get the eligible part of the domain
+          ServerState startSS = domain.getStartState();
+          ServerState endSS   = domain.getEligibleState(
+              replicationServer.getEligibleCN());
+          ChangeNumber fcn = startSS.getMaxChangeNumber(cn.getServerId());
+          ChangeNumber lcn = endSS.getMaxChangeNumber(cn.getServerId());
+
+          // if the draftCNDb change record, is out of the eligible part
+          //  of the domain, then it can be removed.
+          if (cn.older(fcn)||cn.newer(lcn))
+          {
+            cursor.delete();
+          }
+        }
+      }
+      cursor.close();
+      done = true;
+    }
+    catch (DeadlockException e)
+    {
+      cursor.abort();
+      if (tries == DEADLOCK_RETRIES)
+      {
+        // could not handle the Deadlock after DEADLOCK_RETRIES tries.
+        // shutdown the ReplicationServer.
+        shutdown = true;
+        throw (e);
+      }
+    }
+    catch (DatabaseException e)
+    {
+      // mark shutdown for this db so that we don't try again to
+      // stop it from cursor.close() or methods called by cursor.close()
+      shutdown = true;
+      cursor.abort();
+      throw (e);
+    }
+  }
+
+  /**
+   * This internal class is used to implement the Monitoring capabilities
+   * of the dbHandler.
+   */
+  private class DbMonitorProvider extends MonitorProvider<MonitorProviderCfg>
+  {
+    private DbMonitorProvider()
+    {
+      super("ReplicationServer DraftCN Database");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ArrayList<Attribute> getMonitorData()
+    {
+      ArrayList<Attribute> attributes = new ArrayList<Attribute>();
+      attributes.add(Attributes.create("first-draft-changenumber",
+          Integer.toString(db.readFirstSeqnum())));
+      attributes.add(Attributes.create("last-draft-changenumber",
+          Integer.toString(db.readLastDraftCN())));
+      attributes.add(Attributes.create("count",
+          Long.toString(count())));
+      return attributes;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getMonitorInstanceName()
+    {
+      return "ReplicationServer DraftCN database ";
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long getUpdateInterval()
+    {
+      /* we don't wont to do polling on this monitor */
+      return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void initializeMonitorProvider(MonitorProviderCfg configuration)
+                            throws ConfigException,InitializationException
+    {
+      // Nothing to do for now
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void updateMonitorData()
+    {
+      // As long as getUpdateInterval() returns 0, this will never get called
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return("draftCNdb:" + " " + firstkey + " " + lastkey);
+  }
+
+  /**
+   * Set the Purge delay for this db Handler.
+   * @param delay The purge delay in Milliseconds.
+   */
+  public void setPurgeDelay(long delay)
+  {
+    trimage = delay;
+  }
+
+  /**
+   * Clear the changes from this DB (from both memory cache and DB storage).
+   * @throws DatabaseException When an exception occurs while removing the
+   * changes from the DB.
+   * @throws Exception When an exception occurs while accessing a resource
+   * from the DB.
+   *
+   */
+  public void clear() throws DatabaseException, Exception
+  {
+    db.clear();
+    firstkey = db.readFirstSeqnum();
+    lastkey = db.readLastDraftCN();
+  }
+
+  private ReentrantLock lock = new ReentrantLock();
+
+  /**
+   * Tests if the current thread has the lock on this object.
+   * @return True if the current thread has the lock.
+   */
+  public boolean hasLock()
+  {
+    return (lock.getHoldCount() > 0);
+  }
+
+  /**
+   * Takes the lock on this object (blocking until lock can be acquired).
+   * @throws java.lang.InterruptedException If interrupted.
+   */
+   public void lock() throws InterruptedException
+  {
+    lock.lockInterruptibly();
+  }
+
+  /**
+   * Releases the lock on this object.
+   */
+  public void release()
+  {
+    lock.unlock();
+  }
+
+  /**
+   * Get the value associated to a provided key.
+   * @param key the provided key.
+   * @return the associated value, null when none.
+   */
+  public String getValue(int key)
+  {
+    String value = null;
+    DraftCNDBCursor draftCNDBCursor = null;
+    try
+    {
+      draftCNDBCursor = db.openReadCursor(key);
+      value = draftCNDBCursor.currentValue();
+    }
+    catch(Exception e)
+    {
+      if (debugEnabled())
+        TRACER.debugInfo("In DraftCNDbHandler.getGeneralizedState, read: " +
+          " key=" + key + " genServerState returned is null" +
+          " first=" + db.readFirstSeqnum() +
+          " last=" + db.readLastDraftCN() +
+          " count=" + db.count() +
+          " exception" + e + " " + e.getMessage());
+      return null;
+    }
+    finally
+    {
+      if (draftCNDBCursor != null)
+        draftCNDBCursor.close();
+    }
+    return value;
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDbIterator.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDbIterator.java
new file mode 100644
index 0000000..e988aea
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DraftCNDbIterator.java
@@ -0,0 +1,178 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.server.DraftCNDB.*;
+import org.opends.server.types.DebugLogLevel;
+
+import com.sleepycat.je.DatabaseException;
+
+/**
+ * This class allows to iterate through the changes received from a given
+ * LDAP Server Identifier.
+ */
+public class DraftCNDbIterator
+{
+  private static final DebugTracer TRACER = getTracer();
+  private DraftCNDBCursor draftCNDbCursor = null;
+
+  /**
+   * Creates a new ReplicationIterator.
+   * All created iterator must be released by the caller using the
+   * releaseCursor() method.
+   *
+   * @param db           The db where the iterator must be created.
+   * @param startDraftCN The draft CN  after which the iterator
+   *                     must start.
+   * @throws Exception   If there is no other change to push after change
+   *                     with changeNumber number.
+   * @throws DatabaseException If a database problem happened.
+   */
+  public DraftCNDbIterator(DraftCNDB db, int startDraftCN)
+  throws Exception, DatabaseException
+  {
+    draftCNDbCursor = db.openReadCursor(startDraftCN);
+    if (draftCNDbCursor == null)
+    {
+      throw new Exception("no new change");
+    }
+  }
+
+  /**
+   * Getter for the value field (external changelog cookie).
+   * @return The value field (external changelog cookie).
+   */
+  public String getValue()
+  {
+    try
+    {
+      return this.draftCNDbCursor.currentValue();
+    }
+    catch(Exception e)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      return null;
+    }
+  }
+
+  /**
+   * Getter for the serviceID field.
+   * @return The service ID.
+   */
+  public String getServiceID()
+  {
+    try
+    {
+      return this.draftCNDbCursor.currentServiceID();
+    }
+    catch(Exception e)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      return null;
+    }
+  }
+
+  /**
+   * Getter for the replication change number field.
+   * @return The replication change number field.
+   */
+  public ChangeNumber getChangeNumber()
+  {
+    try
+    {
+      ChangeNumber cn = this.draftCNDbCursor.currentChangeNumber();
+      return cn;
+    }
+    catch(Exception e)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      return null;
+    }
+  }
+
+  /**
+   * Getter for the draftCN field.
+   * @return The draft CN field.
+   */
+  public int getDraftCN()
+  {
+    ReplicationDraftCNKey sk = (ReplicationDraftCNKey)this.draftCNDbCursor.key;
+    int currentSeqnum = sk.getDraftCN();
+    return currentSeqnum;
+  }
+
+  /**
+   * Skip to the next record of the database.
+   * @return true if has next, false elsewhere
+   * @throws Exception When exception raised.
+   * @throws DatabaseException When database exception raised.
+   */
+  public boolean next()
+  throws Exception, DatabaseException
+  {
+    boolean hasNext = draftCNDbCursor.next();
+    if (hasNext)
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  /**
+   * Release the resources and locks used by this Iterator.
+   * This method must be called when the iterator is no longer used.
+   * Failure to do it could cause DB deadlock.
+   */
+  public void releaseCursor()
+  {
+    synchronized (this)
+    {
+      if (draftCNDbCursor != null)
+      {
+        draftCNDbCursor.close();
+        draftCNDbCursor = null;
+      }
+    }
+  }
+
+  /**
+   * Called by the Gc when the object is garbage collected
+   * Release the cursor in case the iterator was badly used and releaseCursor
+   * was never called.
+   */
+  protected void finalize()
+  {
+    releaseCursor();
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java
index d0565d6..5272f50 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java
@@ -27,12 +27,14 @@
 package org.opends.server.replication.server;
 
 import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
 import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -41,7 +43,6 @@
 import org.opends.messages.Category;
 import org.opends.messages.Message;
 import org.opends.messages.Severity;
-import org.opends.server.api.DirectoryThread;
 import org.opends.server.replication.common.ChangeNumber;
 import org.opends.server.replication.common.MultiDomainServerState;
 import org.opends.server.replication.common.ServerState;
@@ -52,7 +53,7 @@
 import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.ResultCode;
-import org.opends.server.util.TimeThread;
+import org.opends.server.util.ServerConstants;
 
 /**
  * This class defines a server handler, which handles all interaction with a
@@ -61,128 +62,234 @@
 public class ECLServerHandler extends ServerHandler
 {
 
-  // Properties filled only if remote server is a RS
-  private String serverAddressURL;
-
+  // This is a string identifying the operation, provided by the client part
+  // of the ECL, used to help interpretation of messages logged.
   String operationId;
 
+  // Iterator on the draftCN database.
+  private DraftCNDbIterator draftCNDbIter = null;
+
+  boolean draftCompat = false;
   /**
-   * CLDomainContext : contains the state properties for the search
-   * currently being processed, by replication domain.
+   * Specifies the last draft changer number (seqnum) requested.
    */
-  private class CLDomainContext
+  public int lastDraftCN = 0;
+  /**
+   * Specifies whether the draft change number (seqnum) db has been read until
+   * its end.
+   */
+  public boolean isEndOfDraftCNReached = false;
+  /**
+   * Specifies whether the current search has been requested to be persistent
+   * or not.
+   */
+  public short isPersistent;
+  /**
+   * Specifies the current search phase : INIT or PERSISTENT.
+   */
+  public int searchPhase = INIT_PHASE;
+  /**
+   * Specifies the cookie contained in the request, specifying where
+   * to start serving the ECL.
+   */
+  public String startCookie;
+  /**
+   * Specifies the value of the cookie before the change currently processed
+   * is returned. It is updated with the change number of the change
+   * currently processed (thus becoming the "current" cookie just
+   * before the change is returned.
+   */
+  public MultiDomainServerState previousCookie =
+    new MultiDomainServerState();
+  /**
+   * Specifies the excluded DNs (like cn=admin, ...).
+   */
+  public ArrayList<String> excludedServiceIDs = new ArrayList<String>();
+
+  /**
+   * Eligible changeNumber - only changes older or equal to eligibleCN
+   * are published in the ECL.
+   */
+  public ChangeNumber eligibleCN = null;
+
+  /**
+   * Provides a string representation of this object.
+   * @return the string representation.
+   */
+  public String dumpState()
   {
-    ReplicationServerDomain rsd; // the repl server domain
-    boolean active;              // is the domain still active
-    MessageHandler mh;           // the message handler associated
-    UpdateMsg nextMsg;
-    UpdateMsg nonElligiblemsg;
+    return new String(
+        this.getClass().getCanonicalName() +
+        "[" +
+        "[draftCompat=" + draftCompat +
+        "] [persistent=" + isPersistent +
+        "] [lastDraftCN=" + lastDraftCN +
+        "] [isEndOfDraftCNReached=" + isEndOfDraftCNReached +
+        "] [searchPhase=" + searchPhase +
+        "] [startCookie=" + startCookie +
+        "] [previousCookie=" + previousCookie +
+    "]]");
+  }
+
+  /**
+   * Class that manages the 'by domain' state variables for the search being
+   * currently processed on the ECL.
+   * For example :
+   * if search on 'cn=changelog' is being processed when 2 replicated domains
+   * dc=us and dc=europe are configured, then there will be 2 DomainContext
+   * used, one for ds=us, and one for dc=europe.
+   */
+  private class DomainContext
+  {
+    ReplicationServerDomain rsd;
+
+    boolean active;              // active when there are still changes
+    // supposed eligible for the ECL.
+
+    MessageHandler mh;           // the message handler from which are read
+    // the changes for this domain
+    private UpdateMsg nextMsg;
+    private UpdateMsg nextNonEligibleMsg;
     ServerState startState;
     ServerState currentState;
     ServerState stopState;
 
     /**
-     * Add to the provider buffer a string representation of this object.
+     * {@inheritDoc}
      */
-    public void toString(StringBuilder buffer, int i)
+    @Override
+    public String toString()
     {
-      CLDomainContext xx = clDomCtxts[i];
+      StringBuilder buffer = new StringBuilder();
+      toString(buffer);
+      return buffer.toString();
+    }
+    /**
+     * Provide a string representation of this object for debug purpose..
+     */
+    public void toString(StringBuilder buffer)
+    {
       buffer.append(
-          " clDomCtxts(" + i + ") [act=" + xx.active +
-          " rsd=" + rsd +
-          " nextMsg=" + nextMsg + "(" +
+          "[ [active=" + active +
+          "] [rsd=" + rsd +
+          "] [nextMsg=" + nextMsg + "(" +
           (nextMsg != null?
               new Date(nextMsg.getChangeNumber().getTime()).toString():"")
               + ")" +
-          " nextNonEligibleMsg="      + nonElligiblemsg +
-          " startState=" + startState +
-          " stopState= " + stopState +
-          " currState= " + currentState + "]");
+              "] [nextNonEligibleMsg="      + nextNonEligibleMsg +
+              "] [startState=" + startState +
+              "] [stopState= " + stopState +
+              "] [currentState= " + currentState + "]]");
+    }
+
+    /**
+     * Get the next message elligible regarding
+     * the crossDomain elligible CN. Put it in the context table.
+     * @param opid The operation id.
+     */
+    private void getNextEligibleMessageForDomain(String opid)
+    {
+      if (debugEnabled())
+        TRACER.debugInfo(" In ECLServerHandler, for " + mh.getServiceId() +
+          " getNextEligibleMessageForDomain(" + opid+ ") "
+          + "ctxt=" + toString());
+
+      assert(nextMsg == null);
+      try
+      {
+        // Before get a new message from the domain, evaluate in priority
+        // a message that has not been published to the ECL because it was
+        // not eligible
+        if (nextNonEligibleMsg != null)
+        {
+          boolean hasBecomeEligible =
+            (nextNonEligibleMsg.getChangeNumber().getTime()
+                <= eligibleCN.getTime());
+
+          if (debugEnabled())
+            TRACER.debugInfo(" In ECLServerHandler, for " + mh.getServiceId() +
+                " getNextEligibleMessageForDomain(" + opid+ ") "
+              + " stored nonEligibleMsg " + nextNonEligibleMsg
+              + " has now become eligible regarding "
+              + " the eligibleCN ("+ eligibleCN
+              + " ):" + hasBecomeEligible);
+
+          if (hasBecomeEligible)
+          {
+            // it is now elligible
+            nextMsg = nextNonEligibleMsg;
+            nextNonEligibleMsg = null;
+          }
+          else
+          {
+            // the oldest is still not elligible - let's wait next
+          }
+        }
+        else
+        {
+          // Here comes a new message !!!
+          // non blocking
+          UpdateMsg newMsg = mh.getnextMessage(false);
+
+          if (debugEnabled())
+            TRACER.debugInfo(" In ECLServerHandler, for " + mh.getServiceId() +
+                " getNextEligibleMessageForDomain(" + opid+ ") "
+                + " got new message : "
+                +  " serviceId=[" + mh.getServiceId()
+                + "] [newMsg=" + newMsg + "]" + dumpState());
+
+          // in non blocking mode, return null when no more msg
+          if (newMsg != null)
+          {
+            boolean isEligible = (newMsg.getChangeNumber().getTime()
+                <= eligibleCN.getTime());
+
+            if (debugEnabled())
+              TRACER.debugInfo(" In ECLServerHandler, for " + mh.getServiceId()
+                + " getNextEligibleMessageForDomain(" + opid+ ") "
+                + "newMsg isEligible=" + isEligible + " since "
+                + "newMsg=[" + newMsg.getChangeNumber()
+                + " " + new Date(newMsg.getChangeNumber().getTime()).toString()
+                + "] eligibleCN=[" + eligibleCN
+                + " " + new Date(eligibleCN.getTime()).toString()+"]"
+                + dumpState());
+
+            if (isEligible)
+            {
+              nextMsg = newMsg;
+            }
+            else
+            {
+              nextNonEligibleMsg = newMsg;
+            }
+          }
+        }
+      }
+      catch(Exception e)
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
     }
   }
 
-  // The list of contexts by domain for the current search
-  CLDomainContext[] clDomCtxts = new CLDomainContext[0];
+  // The global list of contexts by domain for the search currently processed.
+  DomainContext[] domainCtxts = new DomainContext[0];
 
-  private void clDomCtxtsToString(String msg)
+  private String clDomCtxtsToString(String msg)
   {
     StringBuilder buffer = new StringBuilder();
     buffer.append(msg+"\n");
-    for (int i=0;i<clDomCtxts.length;i++)
+    for (int i=0;i<domainCtxts.length;i++)
     {
-      clDomCtxts[i].toString(buffer, i);
+      domainCtxts[i].toString(buffer);
       buffer.append("\n");
     }
-    TRACER.debugInfo(
-        "In " + this.getName() + " clDomCtxts: " + buffer.toString());
+    return buffer.toString();
   }
 
-  /**
-   * Class that manages the state variables for the current search on the ECL.
-   */
-  private class CLTraverseCtxt
-  {
-    /**
-     * Specifies the next changer number (seqnum), -1 when not.
-     */
-    public int nextSeqnum;
-    /**
-     * Specifies whether the current search has been requested to be persistent
-     * or not.
-     */
-    public short isPersistent;
-    /**
-     * Specifies the last changer number (seqnum) requested.
-     */
-    public int stopSeqnum;
-    /**
-     * Specifies whether the change number (seqnum) db has been read until
-     * its end.
-     */
-    public boolean endOfSeqnumdbReached = false;
-    /**
-     * Specifies the current search phase.
-     * 1 = init
-     * 2 = persistent
-     */
-    public int searchPhase = 1;
-    /**
-     * Specifies the cookie contained in the request, specifying where
-     * to start serving the ECL.
-     */
-    public String generalizedStartState;
-    /**
-     * Specifies the current cookie value.
-     */
-    public MultiDomainServerState currentCookie =
-      new MultiDomainServerState();
-    /**
-     * Specifies the excluded DNs.
-     */
-    public ArrayList<String> excludedServiceIDs = new ArrayList<String>();
-
-    /**
-     * Provides a string representation of this object.
-     * @return the string representation.
-     */
-    public String toString()
-    {
-      return new String(
-        this.getClass().getCanonicalName() +
-        ":[" +
-        " nextSeqnum=" + nextSeqnum +
-        " persistent=" + isPersistent +
-        " stopSeqnum" + stopSeqnum +
-        " endOfSeqnumdbReached=" + endOfSeqnumdbReached +
-        " searchPhase=" + searchPhase +
-        " generalizedStartState=" + generalizedStartState +
-        "]");
-    }
-
-  }
-
-  // The context of the current search
-  private CLTraverseCtxt cLSearchCtxt = new CLTraverseCtxt();
+  static int UNDEFINED_PHASE = 0;
+  static int INIT_PHASE = 1;
+  static int PERSISTENT_PHASE = 2;
 
   /**
    * Starts this handler based on a start message received from remote server.
@@ -199,10 +306,6 @@
           inECLStartMsg.getVersion());
       generationId = inECLStartMsg.getGenerationId();
       serverURL = inECLStartMsg.getServerURL();
-      int separator = serverURL.lastIndexOf(':');
-      serverAddressURL =
-        session.getRemoteAddress() + ":" + serverURL.substring(separator +
-            1);
       setInitialServerState(inECLStartMsg.getServerState());
       setSendWindowSize(inECLStartMsg.getWindowSize());
       if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
@@ -211,8 +314,6 @@
         // Only V2 protocol has the group id in repl server start message
         this.groupId = inECLStartMsg.getGroupId();
       }
-      // FIXME:ECL Any generationID must be removed, it makes no sense here.
-      oldGenerationId = -100;
     }
     catch(Exception e)
     {
@@ -243,7 +344,7 @@
         replicationServer, rcvWindowSize);
     try
     {
-      setServiceIdAndDomain("cn=changelog");
+      setServiceIdAndDomain(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
     }
     catch(DirectoryException de)
     {
@@ -268,12 +369,12 @@
       StartECLSessionMsg startECLSessionMsg)
   throws DirectoryException
   {
-    // FIXME:ECL queueSize is hard coded to 1 else Handler hangs for some reason
+    // queueSize is hard coded to 1 else super class hangs for some reason
     super(null, 1, replicationServerURL, replicationServerId,
         replicationServer, 0);
     try
     {
-      setServiceIdAndDomain("cn=changelog");
+      setServiceIdAndDomain(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
     }
     catch(DirectoryException de)
     {
@@ -369,115 +470,153 @@
 
   /**
    * Initialize the handler from a provided cookie value.
-   * @param providedGeneralizedStartState The provided cookie value.
+   * @param crossDomainStartState The provided cookie value.
    * @throws DirectoryException When an error is raised.
    */
-  public void initializeCLSearchFromGenState(
-      String providedGeneralizedStartState)
+  public void initializeCLSearchFromGenState(String crossDomainStartState)
   throws DirectoryException
   {
-    this.cLSearchCtxt.nextSeqnum = -1; // will not generate seqnum
-    initializeCLDomCtxts(providedGeneralizedStartState);
+    initializeCLDomCtxts(crossDomainStartState);
+  }
+
+  /**
+   * Initialize the handler from a provided draft first change number.
+   * @param startDraftCN The provided draft first change number.
+   * @throws DirectoryException When an error is raised.
+   */
+  public void initializeCLSearchFromDraftCN(int startDraftCN)
+  throws DirectoryException
+  {
+    String crossDomainStartState;
+
+    draftCompat = true;
+
+    DraftCNDbHandler draftCNDb = replicationServer.getDraftCNDbHandler();
+    if (startDraftCN < 0)
+    {
+      // Request filter does not contain any firstDraftCN
+      // So we'll generate from the beginning of what we have stored here.
+
+      // Get the first DraftCN from DraftCNdb
+      if (draftCNDb.count() == 0)
+      {
+        // db is empty
+        isEndOfDraftCNReached = true;
+        crossDomainStartState = null;
+      }
+      else
+      {
+        // get the generalizedServerState related to the start of the draftDb
+        crossDomainStartState = draftCNDb.getValue(draftCNDb.getFirstKey());
+
+        // Get an iterator to traverse the draftCNDb
+        try
+        {
+          draftCNDbIter =
+            draftCNDb.generateIterator(draftCNDb.getFirstKey());
+        }
+        catch(Exception e)
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+
+          if (draftCNDbIter != null)
+            draftCNDbIter.releaseCursor();
+
+          throw new DirectoryException(
+              ResultCode.OPERATIONS_ERROR,
+              Message.raw(Category.SYNC,
+                  Severity.FATAL_ERROR,"Server Error."));
+        }
+      }
+    }
+    else
+    {
+      // Request filter does contain a startDraftCN
+
+      // Read the draftCNDb to see whether it contains startDraftCN
+      crossDomainStartState = draftCNDb.getValue(startDraftCN);
+
+      if (crossDomainStartState != null)
+      {
+        // startDraftCN is present in the draftCnDb
+        // Get an iterator to traverse the draftCNDb
+        try
+        {
+          draftCNDbIter =
+            draftCNDb.generateIterator(draftCNDb.getFirstKey());
+        }
+        catch(Exception e)
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+
+          if (draftCNDbIter != null)
+            draftCNDbIter.releaseCursor();
+
+          throw new DirectoryException(
+              ResultCode.OPERATIONS_ERROR,
+              Message.raw(Category.SYNC,
+                  Severity.FATAL_ERROR,"Server Error."));
+        }
+      }
+      else
+      {
+        // startDraftCN provided in the request is not present in the draftCnDb
+        // Is the provided startDraftCN <= the potential last DraftCNdb
+
+        // Get the draftLimits (from the eligibleCN got at the beginning of
+        // the operation.
+        int[] limits = getECLDraftCNLimits(eligibleCN);
+
+        if (startDraftCN<=limits[1])
+        {
+          // startDraftCN is between first and last and has never been
+          // returned yet
+          crossDomainStartState = draftCNDb.getValue(draftCNDb.getLastKey());
+          // FIXME:ECL ... ok we'll start from the end of the draftCNDb BUT ...
+          // this is NOT the request of the client !!!!
+        }
+        else
+        {
+          throw new DirectoryException(
+              ResultCode.SUCCESS,
+              Message.raw(Category.SYNC,
+                  Severity.INFORMATION,"Bad value provided for change number "
+                  + " Failed to match a replication state to "+startDraftCN));
+        }
+      }
+    }
+    this.draftCompat = true;
+
+    initializeCLDomCtxts(crossDomainStartState);
+
   }
 
   /**
    * Initialize the context for each domain.
-   * @param providedGeneralizedStartState the provided generalized state
+   * @param providedCookie the provided generalized state
    * @throws DirectoryException When an error occurs.
    */
-  public void initializeCLDomCtxts(String providedGeneralizedStartState)
+  public void initializeCLDomCtxts(String providedCookie)
   throws DirectoryException
   {
     HashMap<String,ServerState> startStates = new HashMap<String,ServerState>();
 
     ReplicationServer rs = replicationServerDomain.getReplicationServer();
 
-    try
-    {
-      // Initialize start state for  all running domains with empty state
-      Iterator<ReplicationServerDomain> rsdk = rs.getCacheIterator();
-      if (rsdk != null)
-      {
-        while (rsdk.hasNext())
-        {
-          // process a domain
-          ReplicationServerDomain rsd = rsdk.next();
-          // skip the changelog domain
-          if (rsd == this.replicationServerDomain)
-            continue;
-          startStates.put(rsd.getBaseDn(), new ServerState());
-        }
-      }
-
-      // Overwrite start state from the cookie provided in the request
-      if ((providedGeneralizedStartState != null) &&
-          (providedGeneralizedStartState.length()>0))
-      {
-        String[] domains = providedGeneralizedStartState.split(";");
-        for (String domainState : domains)
-        {
-          // Split baseDN and serverState
-          String[] fields = domainState.split(":");
-
-          // BaseDN - Check it
-          String domainBaseDNReceived = fields[0];
-          if (!startStates.containsKey(domainBaseDNReceived))
-            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED.get(
-                  "unknown " + domainBaseDNReceived));
-
-          // ServerState
-          ServerState domainServerState = new ServerState();
-          if (fields.length>1)
-          {
-            String strState = fields[1];
-            String[] strCN = strState.split(" ");
-            for (String sr : strCN)
-            {
-              ChangeNumber fromChangeNumber = new ChangeNumber(sr);
-              domainServerState.update(fromChangeNumber);
-            }
-          }
-          startStates.put(domainBaseDNReceived, domainServerState);
-
-          // FIXME: ECL first cookie value check
-          // ECL For each of the provided state, it this state is older
-          // than the older change stored in the replication changelog ....
-          // then a purge occured since the time the cookie was published
-          // it is recommended to do a full resync
-          ReplicationServerDomain rsd =
-            rs.getReplicationServerDomain(domainBaseDNReceived, false);
-          ServerState domainStartState = rsd.getStartState();
-          if (!domainServerState.cover(domainStartState))
-          {
-            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED.get(
-                  "too old cookie provided " + providedGeneralizedStartState
-                  + " first acceptable change for " + rsd.getBaseDn()
-                  + " is " + rsd.getStartState()));
-          }
-        }
-      }
-    }
-    catch(DirectoryException de)
-    {
-      throw de;
-    }
-    catch(Exception e)
-    {
-      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-          ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED.get(
-            "Exception raised: " + e.getMessage()));
-    }
+    // Parse the provided cookie and overwrite startState from it.
+    if ((providedCookie != null) && (providedCookie.length()!=0))
+      startStates =
+        MultiDomainServerState.splitGenStateToServerStates(providedCookie);
 
     try
     {
-      // Now traverse all domains and build the initial changelog context
-      Iterator<ReplicationServerDomain> rsdi = rs.getCacheIterator();
+      // Now traverse all domains and build all the initial contexts :
+      // - the global one : dumpState()
+      // - the domain by domain ones : domainCtxts
+      Iterator<ReplicationServerDomain> rsdi = rs.getDomainIterator();
 
       // Creates the table that will contain the real-time info by domain.
-      clDomCtxts = new CLDomainContext[rs.getCacheSize()-1
-                         -this.cLSearchCtxt.excludedServiceIDs.size()];
+      HashSet<DomainContext> tmpSet = new HashSet<DomainContext>();
       int i =0;
       if (rsdi != null)
       {
@@ -491,73 +630,87 @@
             continue;
 
           // skip the excluded domains
-          boolean excluded = false;
-          for(String excludedServiceID : this.cLSearchCtxt.excludedServiceIDs)
-          {
-            if (excludedServiceID.equalsIgnoreCase(rsd.getBaseDn()))
-            {
-              excluded=true;
-              break;
-            }
-          }
-          if (excluded)
+          if (isServiceIDExcluded(rsd.getBaseDn()))
             continue;
 
-          // Creates the context record
-          CLDomainContext newContext = new CLDomainContext();
-          newContext.active = true;
-          newContext.rsd = rsd;
+          // Creates the new domain context
+          DomainContext newDomainCtxt = new DomainContext();
+          newDomainCtxt.active = true;
+          newDomainCtxt.rsd = rsd;
 
-          if (this.cLSearchCtxt.isPersistent ==
+          // Assign the start state for the domain
+          if (isPersistent ==
             StartECLSessionMsg.PERSISTENT_CHANGES_ONLY)
           {
-            newContext.startState = rsd.getCLElligibleState();
+            newDomainCtxt.startState = rsd.getEligibleState(eligibleCN);
           }
           else
           {
-            newContext.startState = startStates.get(rsd.getBaseDn());
-            newContext.stopState = rsd.getCLElligibleState();
+            newDomainCtxt.startState = startStates.remove(rsd.getBaseDn());
+            if ((providedCookie==null)||(providedCookie.isEmpty()))
+              newDomainCtxt.startState = new ServerState();
+            else
+              if (newDomainCtxt.startState == null)
+                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                    ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED.get(
+                        "missing " + rsd.getBaseDn()));
+            newDomainCtxt.stopState = rsd.getEligibleState(eligibleCN);
           }
-          newContext.currentState = new ServerState();
+          newDomainCtxt.currentState = new ServerState();
 
-          // Creates an unconnected SH
+          // Creates an unconnected SH for the domain
           MessageHandler mh = new MessageHandler(maxQueueSize,
               replicationServerURL, replicationServerId, replicationServer);
           // set initial state
-          mh.setInitialServerState(newContext.startState);
+          mh.setInitialServerState(newDomainCtxt.startState);
           // set serviceID and domain
           mh.setServiceIdAndDomain(rsd.getBaseDn());
-          // register into domain
+          // register the unconnected into the domain
           rsd.registerHandler(mh);
-          newContext.mh = mh;
+          newDomainCtxt.mh = mh;
+
+          previousCookie.update(
+              newDomainCtxt.rsd.getBaseDn(),
+              newDomainCtxt.startState);
 
           // store the new context
-          clDomCtxts[i] = newContext;
+          tmpSet.add(newDomainCtxt);
           i++;
         }
       }
-
-      // the next record from the seqnumdb should be the one
-      cLSearchCtxt.endOfSeqnumdbReached = false;
-      cLSearchCtxt.generalizedStartState = providedGeneralizedStartState;
-
-      // Initializes all domain with the next elligible message
-      for (int j=0; j<clDomCtxts.length; j++)
+      if (!startStates.isEmpty())
       {
-        this.getNextElligibleMessage(j);
-        if (clDomCtxts[j].nextMsg == null)
-          clDomCtxts[j].active = false;
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+            ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED.get(
+                "unknown " + startStates.toString()));
+      }
+      domainCtxts = tmpSet.toArray(new DomainContext[0]);
+
+      // the next record from the DraftCNdb should be the one
+      startCookie = providedCookie;
+
+      // Initializes all domain with the next(first) elligible message
+      for (int j=0; j<domainCtxts.length; j++)
+      {
+        domainCtxts[j].getNextEligibleMessageForDomain(operationId);
+
+        if (domainCtxts[j].nextMsg == null)
+          domainCtxts[j].active = false;
       }
     }
     catch(Exception e)
     {
       TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      // FIXME:ECL do not publish internal exception plumb to the client
       throw new DirectoryException(
           ResultCode.OPERATIONS_ERROR,
           Message.raw(Category.SYNC, Severity.INFORMATION,"Exception raised: " +
-              e.getLocalizedMessage()),
-          e);
+              e),
+              e);
     }
+    if (debugEnabled())
+      TRACER.debugInfo(
+        " initializeCLDomCtxts ends with " + " " + dumpState());
   }
 
   /**
@@ -569,21 +722,22 @@
   }
 
   /**
-   * Shutdown this handler ServerHandler.
+   * Shutdown this handler.
    */
   public void shutdown()
   {
-    for (int i=0;i<clDomCtxts.length;i++)
+    for (int i=0;i<domainCtxts.length;i++)
     {
-      if (!clDomCtxts[i].rsd.unRegisterHandler(clDomCtxts[i].mh))
+      if (!domainCtxts[i].rsd.unRegisterHandler(domainCtxts[i].mh))
       {
-        TRACER.debugInfo(this +" shutdown() Internal error " +
-                " when unregistering "+ clDomCtxts[i].mh);
+        logError(Message.raw(Category.SYNC, Severity.NOTICE,
+            this +" shutdown() - error when unregistering handler "
+            + domainCtxts[i].mh));
       }
-      clDomCtxts[i].rsd.stopServer(clDomCtxts[i].mh);
+      domainCtxts[i].rsd.stopServer(domainCtxts[i].mh);
     }
     super.shutdown();
-    clDomCtxts = null;
+    domainCtxts = null;
   }
 
   /**
@@ -629,7 +783,7 @@
     attributes.add(Attributes.create("External-Changelog-Server",
         serverURL));
 
-    // FIXME:ECL No monitoring exist for ECL.
+    // TODO:ECL No monitoring exist for ECL.
     return attributes;
   }
   /**
@@ -640,8 +794,9 @@
   {
     String localString;
     localString = "External changelog Server ";
-    if (this.cLSearchCtxt==null)
-      localString += serverId + " " + serverURL + " " + getServiceId();
+    if (this.serverId != 0)
+      localString += serverId + " " + serverURL + " " + getServiceId()
+       + " " + this.getOperationId();
     else
       localString += this.getName();
     return localString;
@@ -652,18 +807,9 @@
    */
   public ServerStatus getStatus()
   {
-    // FIXME:ECL Sould ECLServerHandler manage a ServerStatus ?
-    return ServerStatus.INVALID_STATUS;
-  }
-  /**
-   * Retrieves the Address URL for this server handler.
-   *
-   * @return  The Address URL for this server handler,
-   *          in the form of an IP address and port separated by a colon.
-   */
-  public String getServerAddressURL()
-  {
-    return serverAddressURL;
+    // There is no other status possible for the ECL Server Handler to
+    // be normally connected.
+    return ServerStatus.NORMAL_STATUS;
   }
   /**
    * {@inheritDoc}
@@ -684,29 +830,34 @@
   {
 
     //
-    this.following = false; // FIXME:ECL makes no sense for ECLServerHandler ?
-    this.lateQueue.clear(); // FIXME:ECL makes no sense for ECLServerHandler ?
-    this.setConsumerActive(true);
-    this.cLSearchCtxt.searchPhase = 1;
+    //this.following = false; // FIXME:ECL makes no sense for ECLServerHandler ?
+    //this.lateQueue.clear(); // FIXME:ECL makes no sense for ECLServerHandler ?
+    //this.setConsumerActive(true);
+
     this.operationId = startECLSessionMsg.getOperationId();
     this.setName(this.getClass().getCanonicalName()+ " " + operationId);
 
-    if (eligibleCNComputerThread==null)
-      eligibleCNComputerThread = new ECLEligibleCNComputerThread();
-
-    cLSearchCtxt.isPersistent  = startECLSessionMsg.isPersistent();
-    cLSearchCtxt.stopSeqnum    = startECLSessionMsg.getLastDraftChangeNumber();
-    cLSearchCtxt.searchPhase   = 1;
-    cLSearchCtxt.currentCookie = new MultiDomainServerState(
+    isPersistent  = startECLSessionMsg.isPersistent();
+    lastDraftCN   = startECLSessionMsg.getLastDraftChangeNumber();
+    searchPhase   = INIT_PHASE;
+    previousCookie = new MultiDomainServerState(
         startECLSessionMsg.getCrossDomainServerState());
-    cLSearchCtxt.excludedServiceIDs=startECLSessionMsg.getExcludedServiceIDs();
+    excludedServiceIDs = startECLSessionMsg.getExcludedServiceIDs();
+    replicationServer.disableEligibility(excludedServiceIDs);
+    eligibleCN = replicationServer.getEligibleCN();
 
-    //--
-    if (startECLSessionMsg.getECLRequestType()==0)
+    if (startECLSessionMsg.getECLRequestType()==
+      StartECLSessionMsg.REQUEST_TYPE_FROM_COOKIE)
     {
       initializeCLSearchFromGenState(
           startECLSessionMsg.getCrossDomainServerState());
     }
+    else if (startECLSessionMsg.getECLRequestType()==
+      StartECLSessionMsg.REQUEST_TYPE_FROM_DRAFT_CHANGE_NUMBER)
+    {
+      initializeCLSearchFromDraftCN(
+          startECLSessionMsg.getFirstDraftChangeNumber());
+    }
 
     if (session != null)
     {
@@ -735,21 +886,15 @@
       // Resume the writer
       ((ECLServerWriter)writer).resumeWriter();
 
-      // FIXME:ECL Potential race condition if writer not yet resumed here
+      // TODO:ECL Potential race condition if writer not yet resumed here
     }
 
-    if (cLSearchCtxt.isPersistent == StartECLSessionMsg.PERSISTENT_CHANGES_ONLY)
+    if (isPersistent == StartECLSessionMsg.PERSISTENT_CHANGES_ONLY)
     {
-      closePhase1();
+      closeInitPhase();
     }
 
-    /* TODO: Good Draft Compat
-    //--
-    if (startCLMsg.getStartMode()==1)
-    {
-      initializeCLSearchFromProvidedSeqnum(startCLMsg.getSequenceNumber());
-    }
-
+    /* TODO: From replication changenumber
     //--
     if (startCLMsg.getStartMode()==2)
     {
@@ -762,14 +907,14 @@
     {
       // to get the CL first and last
       initializeCLDomCtxts(null); // from start
-      ChangeNumber crossDomainElligibleCN = computeCrossDomainElligibleCN();
+      ChangeNumber crossDomainEligibleCN = computeCrossDomainEligibleCN();
 
       try
       {
         // to get the CL first and last
-        // last rely on the crossDomainElligibleCN thhus must have been
+        // last rely on the crossDomainEligibleCN thhus must have been
         // computed before
-        int[] limits = computeCLLimits(crossDomainElligibleCN);
+        int[] limits = computeCLLimits(crossDomainEligibleCN);
         // Send the response
         CLLimitsMsg msg = new CLLimitsMsg(limits[0], limits[1]);
         session.publish(msg);
@@ -793,11 +938,17 @@
       }
       return;
     }
-    Good Draft Compat */
+     */
 
     // Store into domain
     registerIntoDomain();
 
+    if (debugEnabled())
+      TRACER.debugInfo(
+          this.getName() + " initialized: " +
+          " " + dumpState() + " " +
+          " " + clDomCtxtsToString(""));
+
   }
 
   /**
@@ -812,30 +963,12 @@
   throws DirectoryException
   {
     boolean interrupted = true;
-    ECLUpdateMsg msg = getnextUpdate();
+    ECLUpdateMsg msg = getNextECLUpdate();
 
-    // FIXME:ECL We should refactor so that a SH always have a session
+    // TODO:ECL We should refactor so that a SH always have a session
     if (session == null)
       return msg;
 
-    /*
-     * When we remove a message from the queue we need to check if another
-     * server is waiting in flow control because this queue was too long.
-     * This check might cause a performance penalty an therefore it
-     * is not done for every message removed but only every few messages.
-     */
-    /** FIXME:ECL checkAllSaturation makes no sense for ECLServerHandler ?
-    if (++saturationCount > 10)
-    {
-      saturationCount = 0;
-      try
-      {
-        replicationServerDomain.checkAllSaturation();
-      } catch (IOException e)
-      {
-      }
-    }
-    */
     boolean acquired = false;
     do
     {
@@ -857,16 +990,17 @@
 
   /**
    * Get the next message - non blocking - null when none.
-   *
-   * @param synchronous - not used - always non blocking.
-   * @return the next message - null when none.
+   * This method is currently not used but we don't want to keep the mother
+   * class method since it make no sense for ECLServerHandler.
+   * @param synchronous - not used
+   * @return the next message
    */
   protected UpdateMsg getnextMessage(boolean synchronous)
   {
     UpdateMsg msg = null;
     try
     {
-      ECLUpdateMsg eclMsg = getnextUpdate();
+      ECLUpdateMsg eclMsg = getNextECLUpdate();
       if (eclMsg!=null)
         msg = eclMsg.getUpdateMsg();
     }
@@ -878,60 +1012,24 @@
   }
 
   /**
-   * Get the next external changelog update.
-   *
-   * @return    The ECL update, null when none.
-   * @exception DirectoryException when any problem occurs.
+   * Returns the next update message for the External Changelog (ECL).
+   * @return the ECL update message, null when there aren't anymore.
+   * @throws DirectoryException when an error occurs.
    */
-  protected ECLUpdateMsg getnextUpdate()
+  public ECLUpdateMsg getNextECLUpdate()
   throws DirectoryException
   {
-    return getGeneralizedNextECLUpdate(this.cLSearchCtxt);
-  }
+    ECLUpdateMsg oldestChange = null;
 
-  /**
-   * Computes the cross domain eligible message (non blocking).
-   * Return null when search is covered
-   */
-  private ECLUpdateMsg getGeneralizedNextECLUpdate(CLTraverseCtxt cLSearchCtxt)
-  throws DirectoryException
-  {
-    ECLUpdateMsg theOldestChange = null;
-    try
-    {
+    if (debugEnabled())
       TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
           getMonitorInstanceName() + "," + this +
-          " getGeneralizedNextECLUpdate starts with ctxt="
-          + cLSearchCtxt);
+          " getNextECLUpdate starts: " + dumpState());
 
-      if ((cLSearchCtxt.nextSeqnum != -1) &&
-          (!cLSearchCtxt.endOfSeqnumdbReached))
-      {
-        /* TODO:ECL G Good changelog draft compat.
-        // First time , initialise the cursor to traverse the seqnumdb
-        if (seqnumDbReadIterator == null)
-        {
-          try
-          {
-          seqnumDbReadIterator = replicationServerDomain.getReplicationServer().
-            getSeqnumDbHandler().generateIterator(cLSearchCtxt.nextSeqnum);
-            TRACER.debugInfo("getGeneralizedNextMessage(): "
-                + " creates seqnumDbReadIterator from nextSeqnum="
-                + cLSearchCtxt.nextSeqnum
-                + " 1rst=" + seqnumDbReadIterator.getSeqnum()
-                + " CN=" + seqnumDbReadIterator.getChangeNumber()
-                + cLSearchCtxt);
-          }
-          catch(Exception e)
-          {
-            cLSearchCtxt.endOfSeqnumdbReached = true;
-          }
-        }
-        */
-      }
+    try
+    {
 
-
-      // Search / no seqnum / not persistent
+      // Search / no DraftCN / not persistent
       // -----------------------------------
       //  init: all domain are candidate
       //    get one msg from each
@@ -958,136 +1056,136 @@
       //    if one domain has no msg, still is candidate
 
       int iDom = 0;
-      boolean nextclchange = true;
-      while ((nextclchange) && (cLSearchCtxt.searchPhase==1))
+      boolean continueLooping = true;
+      while ((continueLooping) && (searchPhase == INIT_PHASE))
       {
-
         // Step 1 & 2
-        if (cLSearchCtxt.searchPhase==1)
+        if (searchPhase == INIT_PHASE)
         {
-          if (debugEnabled())
-            clDomCtxtsToString("In getGeneralizedNextMessage : " +
-              "looking for the generalized oldest change");
+          // Normally we whould not loop .. except ...
+          continueLooping = false;
 
-          // Retrieves the index in the subx table of the
-          // generalizedOldestChange
-          iDom = getGeneralizedOldestChange();
+          iDom = getOldestChangeFromDomainCtxts();
 
-          // idomain != -1 means that we got one
-          // generalized oldest change to process
-          if (iDom==-1)
+          // iDom == -1 means that there is no oldest change to process
+          if (iDom == -1)
           {
-            closePhase1();
+            closeInitPhase();
 
-            // signify end of phase 1 to the caller
+            // signals end of phase 1 to the caller
             return null;
           }
 
-          // idomain != -1 means that we got one
-          // generalized oldest change to process
-          String suffix = this.clDomCtxts[iDom].rsd.getBaseDn();
-          theOldestChange = new ECLUpdateMsg(
-              (LDAPUpdateMsg)clDomCtxts[iDom].nextMsg,
-              null, // set later
-              suffix);
+          // Build the ECLUpdateMsg to be returned
+          oldestChange = new ECLUpdateMsg(
+              (LDAPUpdateMsg)domainCtxts[iDom].nextMsg,
+              null, // cookie will be set later
+              domainCtxts[iDom].rsd.getBaseDn(),
+              0); //  draftChangeNumber may be set later
+          domainCtxts[iDom].nextMsg = null;
 
-          nextclchange = false;
-
-          /* TODO:ECL G Good change log draft compat.
-          if (cLSearchCtxt.nextSeqnum!=-1)
+          if (draftCompat)
           {
-            // Should either retrieve or generate a seqnum
-            // we also need to check if the seqnumdb is acccurate reagrding
+            // either retrieve a draftCN from the draftCNDb
+            // or assign a new draftCN and store in the db
+
+            DraftCNDbHandler draftCNDb=replicationServer.getDraftCNDbHandler();
+
+            // We also need to check if the draftCNdb is consistent with
             // the changelogdb.
             // if not, 2 potential reasons
-            // -1 : purge from the changelog .. let's traverse the seqnumdb
-            // -2 : changelog is late .. let's traverse the changelog
+            // a/ : changelog has been purged (trim)let's traverse the draftCNDb
+            // b/ : changelog is late .. let's traverse the changelogDb
+            // The following loop allows to loop until being on the same cn
+            // in the 2 dbs
 
             // replogcn : the oldest change from the changelog db
-            ChangeNumber replogcn = theOldestChange.getChangeNumber();
-            DN replogReplDomDN = clDomCtxts[iDom].rsd.getBaseDn();
+            ChangeNumber cnFromChangelogDb =
+              oldestChange.getUpdateMsg().getChangeNumber();
+            String dnFromChangelogDb = domainCtxts[iDom].rsd.getBaseDn();
 
             while (true)
             {
-              if (!cLSearchCtxt.endOfSeqnumdbReached)
+              if (!isEndOfDraftCNReached)
               {
-                // we did not reach yet the end of the seqnumdb
+                // we did not reach yet the end of the DraftCNdb
 
-                // seqnumcn : the next change from from the seqnum db
-                ChangeNumber seqnumcn = seqnumDbReadIterator.getChangeNumber();
+                // the next change from the DraftCN db
+                ChangeNumber cnFromDraftCNDb = draftCNDbIter.getChangeNumber();
+                String dnFromDraftCNDb = draftCNDbIter.getServiceID();
 
-                // are replogcn and seqnumcn should be the same change ?
-                int cmp = replogcn.compareTo(seqnumcn);
-                DN seqnumReplDomDN=DN.decode(seqnumDbReadIterator.
-                getDomainDN());
+                // are replogcn and DraftCNcn should be the same change ?
+                int areCNEqual = cnFromChangelogDb.compareTo(cnFromDraftCNDb);
+                int areDNEqual = dnFromChangelogDb.compareTo(dnFromDraftCNDb);
 
-                TRACER.debugInfo("seqnumgen: comparing the 2 db "
-                    + " changelogdb:" + replogReplDomDN + "=" + replogcn
-                    + " ts=" +
-                    new Date(replogcn.getTime()).toString()
-                    + "## seqnumdb:" + seqnumReplDomDN + "=" + seqnumcn
-                    + " ts=" +
-                    new Date(seqnumcn.getTime()).toString()
-                    + " sn older=" + seqnumcn.older(replogcn));
+                if (debugEnabled())
+                  TRACER.debugInfo("getNextECLUpdate generating draftCN "
+                    + " comparing the 2 db DNs :"
+                    + dnFromChangelogDb + "?=" + cnFromChangelogDb
+                    + " timestamps:" + new Date(cnFromChangelogDb.getTime())
+                    + " ?older" +   new Date(cnFromDraftCNDb.getTime()));
 
-                if ((replogReplDomDN.compareTo(seqnumReplDomDN)==0) && (cmp==0))
+                if ((areDNEqual==0) && (areCNEqual==0))
                 {
                   // same domain and same CN => same change
 
-                  // assign the seqnum from the seqnumdb
-                  // to the change from the changelogdb
+                  // assign the DraftCN found to the change from the changelogdb
+                  if (debugEnabled())
+                    TRACER.debugInfo("getNextECLUpdate generating draftCN "
+                      + " assigning draftCN=" + draftCNDbIter.getDraftCN()
+                      + " to change=" + oldestChange);
 
-                  TRACER.debugInfo("seqnumgen: assigning seqnum="
-                      + seqnumDbReadIterator.getSeqnum()
-                      + " to change=" + theOldestChange);
-                  theOldestChange.setSeqnum(seqnumDbReadIterator.getSeqnum());
+                  oldestChange.setDraftChangeNumber(
+                      draftCNDbIter.getDraftCN());
 
-                  // prepare the next seqnum for the potential next change added
-                  // to the seqnumDb
-                  cLSearchCtxt.nextSeqnum = seqnumDbReadIterator.getSeqnum()
-                  + 1;
                   break;
                 }
                 else
                 {
-                  // replogcn and seqnumcn are NOT the same change
-                  if (seqnumcn.older(replogcn))
+                  // replogcn and DraftCNcn are NOT on the same change
+                  if (cnFromDraftCNDb.older(cnFromChangelogDb))
                   {
-                    // the change from the seqnumDb is older
+                    // the change from the DraftCNDb is older
                     // that means that the change has been purged from the
-                    // changelog
+                    // changelogDb (and DraftCNdb not yet been trimed)
+
                     try
                     {
-                      // let's traverse the seqnumdb searching for the change
+                      // let's traverse the DraftCNdb searching for the change
                       // found in the changelogDb.
-                      TRACER.debugInfo("seqnumgen: will skip "
-                          + seqnumcn  + " and next from  the seqnum");
-                      cLSearchCtxt.endOfSeqnumdbReached =
-                        (seqnumDbReadIterator.next()==false);
-                      TRACER.debugInfo("seqnumgen: has nexted cr to "
-                          + " sn=" + seqnumDbReadIterator.getSeqnum()
-                          + " cn=" + seqnumDbReadIterator.getChangeNumber()
-                          + " and reached end "
-                          + " of seqnumdb:"+cLSearchCtxt.endOfSeqnumdbReached);
-                      if (cLSearchCtxt.endOfSeqnumdbReached)
+                      TRACER.debugInfo("getNextECLUpdate generating draftCN "
+                          + " will skip " + cnFromDraftCNDb
+                          + " and read next change from the DraftCNDb.");
+
+                      isEndOfDraftCNReached = (draftCNDbIter.next()==false);
+
+                      TRACER.debugInfo("getNextECLUpdate generating draftCN "
+                          + " has skiped to "
+                          + " sn=" + draftCNDbIter.getDraftCN()
+                          + " cn=" + draftCNDbIter.getChangeNumber()
+                          + " End of draftCNDb ?"+isEndOfDraftCNReached);
+
+                      if (isEndOfDraftCNReached)
                       {
-                        // we are at the end of the seqnumdb in the append mode
-                        // store in seqnumdb the pair
-                        // seqnum of the cur change,state before this change)
-                        replicationServerDomain.addSeqnum(
-                            cLSearchCtxt.nextSeqnum,
-                            getGenState(),
-                      clDomCtxts[iDom].rsd.getBaseDn().toNormalizedString(),
-                            theOldestChange.getChangeNumber());
-                        theOldestChange.setSeqnum(cLSearchCtxt.nextSeqnum);
-                        cLSearchCtxt.nextSeqnum++;
+                        // we are at the end of the DraftCNdb in the append mode
+
+                        // generate a new draftCN and assign to this change
+                        oldestChange.setDraftChangeNumber(
+                            replicationServer.getNewDraftCN());
+
+                        // store in DraftCNdb the pair
+                        // (draftCN_of_the_cur_change, state_before_this_change)
+                        draftCNDb.add(
+                            oldestChange.getDraftChangeNumber(),
+                            previousCookie.toString(),
+                            oldestChange.getServiceId(),
+                            oldestChange.getUpdateMsg().getChangeNumber());
+
                         break;
                       }
                       else
                       {
-                        // next change from seqnumdb
-                        cLSearchCtxt.nextSeqnum =
-                          seqnumDbReadIterator.getSeqnum() + 1;
+                        // let's go to test this new change fro the DraftCNdb
                         continue;
                       }
                     }
@@ -1100,108 +1198,99 @@
                     // the change from the changelogDb is older
                     // it should have been stored lately
                     // let's continue to traverse the changelogdb
-                    TRACER.debugInfo("seqnumgen: will skip "
-                        + replogcn  + " and next from  the CL");
-                    nextclchange = true;
+                    TRACER.debugInfo("getNextECLUpdate: will skip "
+                        + cnFromChangelogDb
+                        + " and read next from the regular changelog.");
+                    continueLooping = true;
                     break; // TO BE CHECKED
                   }
                 }
               }
               else
               {
-                // we are at the end of the seqnumdb in the append mode
-                // store in seqnumdb the pair
-                // (seqnum of the current change, state before this change)
-                replicationServerDomain.addSeqnum(
-                    cLSearchCtxt.nextSeqnum,
-                    getGenState(),
-                    clDomCtxts[iDom].rsd.getBaseDn().toNormalizedString(),
-                    theOldestChange.getChangeNumber());
-                theOldestChange.setSeqnum(cLSearchCtxt.nextSeqnum);
-                cLSearchCtxt.nextSeqnum++;
+                // we are at the end of the DraftCNdb in the append mode
+                // store in DraftCNdb the pair
+                // (DraftCN of the current change, state before this change)
+                oldestChange.setDraftChangeNumber(
+                    replicationServer.getNewDraftCN());
+
+                draftCNDb.add(
+                    oldestChange.getDraftChangeNumber(),
+                    this.previousCookie.toString(),
+                    domainCtxts[iDom].rsd.getBaseDn(),
+                    oldestChange.getUpdateMsg().getChangeNumber());
+
                 break;
               }
-            } // while seqnum
-          } // nextseqnum !- -1
-          */
+            } // while DraftCN
+          }
 
-          // here we have the right oldest change and in the seqnum case we
-          // have its seqnum
+          // here we have the right oldest change
+          // and in the draft case, we have its draft changenumber
 
           // Set and test the domain of the oldestChange see if we reached
           // the end of the phase for this domain
-          clDomCtxts[iDom].currentState.update(
-              theOldestChange.getUpdateMsg().getChangeNumber());
+          domainCtxts[iDom].currentState.update(
+              oldestChange.getUpdateMsg().getChangeNumber());
 
-          if (clDomCtxts[iDom].currentState.cover(clDomCtxts[iDom].stopState))
+          if (domainCtxts[iDom].currentState.cover(domainCtxts[iDom].stopState))
           {
-            clDomCtxts[iDom].active = false;
+            domainCtxts[iDom].active = false;
           }
 
-          // Test the seqnum of the oldestChange see if we reached
-          // the end of operation
-          /* TODO:ECL G Good changelog draft compat. Not yet implemented
-          if ((cLSearchCtxt.stopSeqnum>0) &&
-              (theOldestChange.getSeqnum()>=cLSearchCtxt.stopSeqnum))
-          {
-            closePhase1();
-
-            // means end of phase 1 to the calling writer
-            return null;
-          }
-          */
-
-          if (clDomCtxts[iDom].active)
+          if (domainCtxts[iDom].active)
           {
             // populates the table with the next eligible msg from idomain
             // in non blocking mode, return null when no more eligible msg
-            getNextElligibleMessage(iDom);
+            domainCtxts[iDom].getNextEligibleMessageForDomain(operationId);
           }
-        } // phase ==1
-      } // while (nextclchange)
+        } // phase == INIT_PHASE
+      } // while (...)
 
-      if (cLSearchCtxt.searchPhase==2)
+      if (searchPhase == PERSISTENT_PHASE)
       {
-        clDomCtxtsToString("In getGeneralizedNextMessage (persistent): " +
-        "looking for the generalized oldest change");
+        if (debugEnabled())
+          clDomCtxtsToString("In getNextECLUpdate (persistent): " +
+          "looking for the generalized oldest change");
 
-        for (int ido=0; ido<clDomCtxts.length; ido++)
+        for (int ido=0; ido<domainCtxts.length; ido++)
         {
           // get next msg
-          getNextElligibleMessage(ido);
+          domainCtxts[ido].getNextEligibleMessageForDomain(operationId);
         }
 
         // take the oldest one
-        iDom = getGeneralizedOldestChange();
+        iDom = getOldestChangeFromDomainCtxts();
 
         if (iDom != -1)
         {
-          String suffix = this.clDomCtxts[iDom].rsd.getBaseDn();
+          String suffix = this.domainCtxts[iDom].rsd.getBaseDn();
 
-          theOldestChange = new ECLUpdateMsg(
-              (LDAPUpdateMsg)clDomCtxts[iDom].nextMsg,
+          oldestChange = new ECLUpdateMsg(
+              (LDAPUpdateMsg)domainCtxts[iDom].nextMsg,
               null, // set later
-              suffix);
+              suffix, 0);
+          domainCtxts[iDom].nextMsg = null; // clean
 
-          clDomCtxts[iDom].currentState.update(
-              theOldestChange.getUpdateMsg().getChangeNumber());
+          domainCtxts[iDom].currentState.update(
+              oldestChange.getUpdateMsg().getChangeNumber());
 
-          /* TODO:ECL G Good changelog draft compat.
-          if (cLSearchCtxt.nextSeqnum!=-1)
+          if (draftCompat)
           {
-            // should generate seqnum
+            // should generate DraftCN
+            DraftCNDbHandler draftCNDb =replicationServer.getDraftCNDbHandler();
 
-            // store in seqnumdb the pair
-            // (seqnum of the current change, state before this change)
-            replicationServerDomain.addSeqnum(
-                cLSearchCtxt.nextSeqnum,
-                getGenState(),
-                clDomCtxts[iDom].rsd.getBaseDn().toNormalizedString(),
-                theOldestChange.getChangeNumber());
-            theOldestChange.setSeqnum(cLSearchCtxt.nextSeqnum);
-            cLSearchCtxt.nextSeqnum++;
+            oldestChange.setDraftChangeNumber(
+                replicationServer.getNewDraftCN());
+
+            // store in DraftCNdb the pair
+            // (DraftCN of the current change, state before this change)
+            draftCNDb.add(
+                oldestChange.getDraftChangeNumber(),
+                this.previousCookie.toString(),
+                domainCtxts[iDom].rsd.getBaseDn(),
+                oldestChange.getUpdateMsg().getChangeNumber());
           }
-          */
         }
       }
     }
@@ -1214,39 +1303,48 @@
           e);
     }
 
-    if (theOldestChange != null)
+    if (oldestChange != null)
     {
+      if (debugEnabled())
+        TRACER.debugInfo("getNextECLUpdate updates previousCookie:"
+          + oldestChange.getUpdateMsg().getChangeNumber());
+
       // Update the current state
-      this.cLSearchCtxt.currentCookie.update(
-          theOldestChange.getServiceId(),
-          theOldestChange.getUpdateMsg().getChangeNumber());
+      previousCookie.update(
+          oldestChange.getServiceId(),
+          oldestChange.getUpdateMsg().getChangeNumber());
 
       // Set the current value of global state in the returned message
-      theOldestChange.setCookie(this.cLSearchCtxt.currentCookie);
+      oldestChange.setCookie(previousCookie);
+
+      if (debugEnabled())
+        TRACER.debugInfo("getNextECLUpdate returns result oldest change =" +
+                oldestChange);
+
     }
-    return theOldestChange;
+    return oldestChange;
   }
 
   /**
    * Terminates the first (non persistent) phase of the search on the ECL.
    */
-  private void closePhase1()
+  private void closeInitPhase()
   {
     // starvation of changelog messages
     // all domain have been unactived means are covered
     if (debugEnabled())
       TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
-        getMonitorInstanceName() + "," + this + " closePhase1()"
-        + " searchCtxt=" + cLSearchCtxt);
+          getMonitorInstanceName() + "," + this + " closeInitPhase(): "
+          + dumpState());
 
     // go to persistent phase if one
-    for (int i=0; i<clDomCtxts.length; i++)
-      clDomCtxts[i].active = true;
+    for (int i=0; i<domainCtxts.length; i++)
+      domainCtxts[i].active = true;
 
-    if (this.cLSearchCtxt.isPersistent != StartECLSessionMsg.NON_PERSISTENT)
+    if (this.isPersistent != StartECLSessionMsg.NON_PERSISTENT)
     {
-      // phase = 1 done AND persistent search => goto phase 2
-      cLSearchCtxt.searchPhase=2;
+      // INIT_PHASE is done AND search is persistent => goto PERSISTENT_PHASE
+      searchPhase = PERSISTENT_PHASE;
 
       if (writer ==null)
       {
@@ -1257,269 +1355,53 @@
     }
     else
     {
-      // phase = 1 done AND !persistent search => reinit to phase 0
-      cLSearchCtxt.searchPhase=0;
+      // INIT_PHASE is done AND search is not persistent => reinit
+      searchPhase = UNDEFINED_PHASE;
     }
 
-    /* TODO:ECL G Good changelog draft compat.
-    if (seqnumDbReadIterator!=null)
+    if (draftCNDbIter!=null)
     {
-      // End of phase 1 => always release the seqnum iterator
-      seqnumDbReadIterator.releaseCursor();
-      seqnumDbReadIterator = null;
+      // End of INIT_PHASE => always release the iterator
+      draftCNDbIter.releaseCursor();
+      draftCNDbIter = null;
     }
-     */
   }
 
   /**
-   * Get the oldest change contained in the subx table.
-   * The subx table should be populated before
-   * @return the oldest change.
+   * Get the index in the domainCtxt table of the domain with the oldest change.
+   * @return the index of the domain with the oldest change, -1 when none.
    */
-  private int getGeneralizedOldestChange()
+  private int getOldestChangeFromDomainCtxts()
   {
     int oldest = -1;
-    for (int i=0; i<clDomCtxts.length; i++)
+    for (int i=0; i<domainCtxts.length; i++)
     {
-      if ((clDomCtxts[i].active))
+      if ((domainCtxts[i].active))
       {
         // on the first loop, oldest==-1
         // .msg is null when the previous (non blocking) nextMessage did
         // not have any eligible msg to return
-        if (clDomCtxts[i].nextMsg != null)
+        if (domainCtxts[i].nextMsg != null)
         {
           if ((oldest==-1) ||
-              (clDomCtxts[i].nextMsg.compareTo(clDomCtxts[oldest].nextMsg)<0))
+              (domainCtxts[i].nextMsg.compareTo(domainCtxts[oldest].nextMsg)<0))
           {
             oldest = i;
           }
         }
       }
     }
+
     if (debugEnabled())
       TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
-        getMonitorInstanceName()
-        + "," + this + " getGeneralizedOldestChange() " +
-        " returns " +
-        ((oldest!=-1)?clDomCtxts[oldest].nextMsg:""));
+          getMonitorInstanceName()
+          + "," + this + " getOldestChangeFromDomainCtxts() returns " +
+          ((oldest!=-1)?domainCtxts[oldest].nextMsg:"-1"));
 
     return oldest;
   }
 
   /**
-   * Get from the provided domain, the next message elligible regarding
-   * the crossDomain elligible CN. Put it in the context table.
-   * @param idomain the provided domain.
-   */
-  private void getNextElligibleMessage(int idomain)
-  {
-    ChangeNumber crossDomainElligibleCN = computeCrossDomainElligibleCN();
-    try
-    {
-      if (clDomCtxts[idomain].nonElligiblemsg != null)
-      {
-        TRACER.debugInfo("getNextElligibleMessage tests if the already " +
-            " stored nonElligibleMsg has becoem elligible regarding " +
-            " the crossDomainElligibleCN ("+crossDomainElligibleCN +
-            " ) " +
-            clDomCtxts[idomain].nonElligiblemsg.getChangeNumber().
-            older(crossDomainElligibleCN));
-        // we already got the oldest msg and it was not elligible
-        if (clDomCtxts[idomain].nonElligiblemsg.getChangeNumber().
-            older(crossDomainElligibleCN))
-        {
-          // it is now elligible
-          clDomCtxts[idomain].nextMsg = clDomCtxts[idomain].nonElligiblemsg;
-          clDomCtxts[idomain].nonElligiblemsg = null;
-        }
-        else
-        {
-          // the oldest is still not elligible - let's wait next
-        }
-      }
-      else
-      {
-        // non blocking
-        UpdateMsg newMsg = clDomCtxts[idomain].mh.getnextMessage(false);
-        if (debugEnabled())
-          TRACER.debugInfo(this +
-            " getNextElligibleMessage got the next changelogmsg "
-            + " from " + clDomCtxts[idomain].mh.getServiceId()
-            + " newCLMsg=" + newMsg);
-        clDomCtxts[idomain].nextMsg =
-          clDomCtxts[idomain].nonElligiblemsg = null;
-        // in non blocking mode, return null when no more msg
-        if (newMsg != null)
-        {
-          /* TODO:ECL Take into account eligibility.
-          TRACER.debugInfo("getNextElligibleMessage is "
-              + newMsg.getChangeNumber()
-              + new Date(newMsg.getChangeNumber().getTime()).toString()
-              + " elligible "
-              + newMsg.getChangeNumber().older(crossDomainElligibleCN));
-          if (newMsg.getChangeNumber().older(crossDomainElligibleCN))
-          {
-            // is elligible
-            clDomCtxts[idomain].nextMsg = newMsg;
-          }
-          else
-          {
-            // is not elligible
-            clDomCtxts[idomain].nonElligiblemsg = newMsg;
-          }
-          */
-          clDomCtxts[idomain].nextMsg = newMsg;
-        }
-      }
-    }
-    catch(Exception e)
-    {
-      TRACER.debugCaught(DebugLogLevel.ERROR, e);
-    }
-  }
-
-  /*
-   */
-  ECLEligibleCNComputerThread eligibleCNComputerThread = null;
-  ChangeNumber liveecn;
-  private ChangeNumber computeCrossDomainElligibleCN()
-  {
-    return liveecn;
-  }
-
-
-  /**
-   * This class specifies the thread that computes periodically
-   * the cross domain eligible CN.
-   */
-  private final class ECLEligibleCNComputerThread
-    extends DirectoryThread
-  {
-    /**
-     * The tracer object for the debug logger.
-     */
-    private boolean shutdown = false;
-
-    private ECLEligibleCNComputerThread()
-    {
-      super("ECL eligible CN computer thread");
-      start();
-    }
-
-    public void run()
-    {
-      while (shutdown == false)
-      {
-        try {
-          synchronized (this)
-          {
-            liveecn = computeNewCrossDomainElligibleCN();
-            try
-            {
-              this.wait(1000);
-            } catch (InterruptedException e)
-            { }
-          }
-        } catch (Exception end)
-        {
-          break;
-        }
-      }
-    }
-
-    private ChangeNumber computeNewCrossDomainElligibleCN()
-    {
-      ChangeNumber computedCrossDomainElligibleCN = null;
-      String s = "=> ";
-
-      ReplicationServer rs = replicationServerDomain.getReplicationServer();
-
-      if (debugEnabled())
-        TRACER.debugInfo("ECLSH.computeNewCrossDomainElligibleCN() "
-          + " periodic starts rs="+rs);
-
-      Iterator<ReplicationServerDomain> rsdi = rs.getCacheIterator();
-      if (rsdi != null)
-      {
-        while (rsdi.hasNext())
-        {
-          ReplicationServerDomain domain = rsdi.next();
-          if (domain.getBaseDn().equalsIgnoreCase("cn=changelog"))
-            continue;
-
-          ChangeNumber domainElligibleCN = computeEligibleCN(domain);
-          if (domainElligibleCN==null)
-            continue;
-          if ((computedCrossDomainElligibleCN == null) ||
-              (domainElligibleCN.older(computedCrossDomainElligibleCN)))
-          {
-            computedCrossDomainElligibleCN = domainElligibleCN;
-          }
-          s += "\n DN:" + domain.getBaseDn()
-          + "\t\t domainElligibleCN :" + domainElligibleCN
-          + "/" +
-          new Date(domainElligibleCN.getTime()).toString();
-        }
-      }
-
-      if (debugEnabled())
-        TRACER.debugInfo("SH.computeNewCrossDomainElligibleCN() periodic " +
-          " ends with " +
-          " the following domainElligibleCN for each domain :" + s +
-          "\n thus CrossDomainElligibleCN=" + computedCrossDomainElligibleCN +
-          "  ts=" +
-          new Date(computedCrossDomainElligibleCN.getTime()).toString());
-
-      return computedCrossDomainElligibleCN;
-    }
-  }
-
-  /**
-   * Compute the eligible CN.
-   * @param rsd The provided replication server domain for which we want
-   * to retrieve the eligible date.
-   * @return null if the domain does not play in eligibility.
-   */
-  public ChangeNumber computeEligibleCN(ReplicationServerDomain rsd)
-  {
-    ChangeNumber elligibleCN = null;
-    ServerState heartbeatState = rsd.getHeartbeatState();
-    if (heartbeatState==null)
-      return null;
-
-    // compute elligible CN
-    ServerState hbState = heartbeatState.duplicate();
-
-    Iterator<Short> it = hbState.iterator();
-    while (it.hasNext())
-    {
-      short sid = it.next();
-      ChangeNumber storedCN = hbState.getMaxChangeNumber(sid);
-
-      // If the most recent UpdateMsg or CLHeartbeatMsg received is very old
-      // then the server is considered down and not considered for eligibility
-      if (TimeThread.getTime()-storedCN.getTime()>2000)
-      {
-        if (debugEnabled())
-          TRACER.debugInfo(
-            "For RSD." + rsd.getBaseDn() + " Server " + sid
-            + " is not considered for eligibility ... potentially down");
-        continue;
-      }
-
-      if ((elligibleCN == null) || (storedCN.older(elligibleCN)))
-      {
-        elligibleCN = storedCN;
-      }
-    }
-
-    if (debugEnabled())
-      TRACER.debugInfo(
-        "For RSD." + rsd.getBaseDn() + " ElligibleCN()=" + elligibleCN);
-    return elligibleCN;
-  }
-
-  /**
    * Returns the client operation id.
    * @return The client operation id.
    */
@@ -1533,7 +1415,7 @@
    * @return Whether the current search is persistent or not.
    */
   public short isPersistent() {
-    return this.cLSearchCtxt.isPersistent;
+    return this.isPersistent;
   }
 
   /**
@@ -1541,7 +1423,137 @@
    * @return Whether the current search is persistent or not.
    */
   public int getSearchPhase() {
-    return this.cLSearchCtxt.searchPhase;
+    return this.searchPhase;
   }
 
+  /**
+   * Refresh the eligibleCN by requesting the replication server.
+   */
+  public void refreshEligibleCN()
+  {
+    eligibleCN = replicationServer.getEligibleCN();
+  }
+
+  /*
+   * Get first and last DraftCN
+   * @param crossDomainEligibleCN
+   * @return
+   */
+  private int[] getECLDraftCNLimits(ChangeNumber crossDomainEligibleCN)
+  throws DirectoryException
+  {
+    /* The content of the DraftCNdb depends on the SEARCH operations done before
+     * requesting the DraftCN. If no operations, DraftCNdb is empty.
+     * The limits we want to get are the "potential" limits if a request was
+     * done, the DraftCNdb is probably not complete to do that.
+     *
+     * The first DraftCN is :
+     *  - the first record from the DraftCNdb
+     *  - if none because DraftCNdb empty,
+     *      then
+     *        if no change in replchangelog then return 0
+     *        else return 1 (DraftCN that WILL be returned to next search)
+     *
+     * The last DraftCN is :
+     *  - initialized with the last record from the DraftCNdb (0 if none)
+     *    and consider the genState associated
+     *  - to the last DraftCN, we add the count of updates in the replchangelog
+     *     FROM that genState TO the crossDomainEligibleCN
+     *     (this diff is done domain by domain)
+     */
+
+    int firstDraftCN;
+    int lastDraftCN;
+    boolean DraftCNdbIsEmpty;
+    DraftCNDbHandler draftCNDbH = replicationServer.getDraftCNDbHandler();
+
+    ReplicationServer rs = replicationServerDomain.getReplicationServer();
+
+    // Get the first DraftCN from the DraftCNdb
+    firstDraftCN = draftCNDbH.getFirstKey();
+    HashMap<String,ServerState> domainsServerStateForLastSeqnum = null;
+    if (firstDraftCN < 1)
+    {
+      DraftCNdbIsEmpty=true;
+      firstDraftCN = 0;
+      lastDraftCN = 0;
+    }
+    else
+    {
+      DraftCNdbIsEmpty=false;
+
+      // Get the last DraftCN from the DraftCNdb
+      lastDraftCN = draftCNDbH.getLastKey();
+
+      // Get the generalized state associated with the current last DraftCN
+      // and initializes from it the startStates table
+      String lastSeqnumGenState = draftCNDbH.getValue(lastDraftCN);
+      if ((lastSeqnumGenState != null) && (lastSeqnumGenState.length()>0))
+      {
+        domainsServerStateForLastSeqnum = MultiDomainServerState.
+          splitGenStateToServerStates(lastSeqnumGenState);
+      }
+    }
+
+    // Domain by domain
+    Iterator<ReplicationServerDomain> rsdi = rs.getDomainIterator();
+    if (rsdi != null)
+    {
+      while (rsdi.hasNext())
+      {
+        // process a domain
+        ReplicationServerDomain rsd = rsdi.next();
+
+        if (isServiceIDExcluded(rsd.getBaseDn()))
+          continue;
+
+        // for this domain, have the state in the replchangelog
+        // where the last DraftCN update is
+        ServerState domainServerStateForLastSeqnum;
+        if ((domainsServerStateForLastSeqnum == null) ||
+            (domainsServerStateForLastSeqnum.get(rsd.getBaseDn())==null))
+        {
+          domainServerStateForLastSeqnum = new ServerState();
+        }
+        else
+        {
+          domainServerStateForLastSeqnum =
+            domainsServerStateForLastSeqnum.get(rsd.getBaseDn());
+        }
+
+        // Count the number of (eligible) changes from this place
+        // to the eligible CN (cross server)
+        long ec = rsd.getEligibleCount(
+            domainServerStateForLastSeqnum, crossDomainEligibleCN);
+
+        // ... hum ...
+        if ((ec>0) && (DraftCNdbIsEmpty==false))
+          ec--;
+
+        // cumulates on domains
+        lastDraftCN += ec;
+
+        // DraftCN is empty and there are eligible updates in the repl changelog
+        // then init first DraftCN
+        if ((ec>0) && (firstDraftCN==0))
+          firstDraftCN = 1;
+      }
+    }
+    return new int[]{firstDraftCN, lastDraftCN};
+  }
+
+  private boolean isServiceIDExcluded(String serviceID)
+  {
+    // skip the excluded domains
+    boolean excluded = false;
+    for(String excludedServiceID : this.excludedServiceIDs)
+    {
+      if (excludedServiceID.equalsIgnoreCase(serviceID))
+      {
+        excluded=true;
+        break;
+      }
+    }
+    return excluded;
+  }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java
index 642fa9c..8939810 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java
@@ -66,6 +66,7 @@
   private short protocolVersion = -1;
   private boolean suspended;
   private boolean shutdown;
+  private PersistentSearch mypsearch;
 
   /**
    * Create a ServerWriter.
@@ -80,7 +81,7 @@
   {
     super(session, (short)-1, handler, replicationServerDomain);
 
-    setName("Replication ECL Writer Thread for op:" +
+    setName("Replication ECL Writer Thread for operation " +
         handler.getOperationId());
 
     this.session = session;
@@ -90,6 +91,21 @@
     this.protocolVersion = handler.getProtocolVersion();
     this.suspended = false;
     this.shutdown = false;
+
+    // Look for the psearch object related to this operation , the one that
+    // will ne notified with new entries to be returned.
+    ECLWorkflowElement wfe = (ECLWorkflowElement)
+    DirectoryServer.getWorkflowElement(
+        ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
+    for (PersistentSearch psearch : wfe.getPersistentSearches())
+    {
+      if (psearch.getSearchOperation().toString().equals(
+          handler.getOperationId()))
+      {
+        mypsearch = psearch;
+        break;
+      }
+    }
   }
 
   /**
@@ -139,18 +155,14 @@
         }
 
         if (shutdown)
-        {
           return;
-        }
 
         // Not suspended
         doIt();
 
-
         if (shutdown)
-        {
           return;
-        }
+
         suspendWriter();
       }
     }
@@ -186,7 +198,7 @@
   }
 
   /**
-   * Loop gettting changes from the domain and publishing them either to
+   * Loop geting changes from the domain and publishing them either to
    * the provided session or to the ECL session interface.
    * @throws IOException when raised (connection closure)
    * @throws InterruptedException when raised
@@ -213,6 +225,7 @@
       {
         try
         {
+          handler.refreshEligibleCN();
           update = handler.takeECLUpdate();
         }
         catch(DirectoryException de)
@@ -273,7 +286,8 @@
   throws IOException
   {
     if (debugEnabled())
-      TRACER.debugInfo(this + " publishes msg=[" + msg.toString() + "]");
+      TRACER.debugInfo(this.getName() +
+          " publishes msg=[" + msg.toString() + "]");
 
     if (session!=null)
     {
@@ -281,17 +295,12 @@
     }
     else
     {
-      ECLWorkflowElement wfe = (ECLWorkflowElement)
-      DirectoryServer.getWorkflowElement(
-          ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
-
-      // Notify persistent searches.
-      for (PersistentSearch psearch : wfe.getPersistentSearches())
+      if (mypsearch != null)
       {
         try
         {
           Entry eclEntry = ECLSearchOperation.createEntryFromMsg(msg);
-          psearch.processAdd(eclEntry, -1);
+          mypsearch.processAdd(eclEntry, -1);
         }
         catch(Exception e)
         {
@@ -299,6 +308,7 @@
             ERR_WRITER_UNEXPECTED_EXCEPTION.get(handler.toString() +
               " " +  stackTraceToSingleLineString(e));
           logError(errMessage);
+          mypsearch.cancel();
         }
       }
     }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java
index 0c6bfea..2d3ec71 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java
@@ -87,7 +87,7 @@
   public ECLUpdateMsg getNextUpdate()
   throws DirectoryException
   {
-    return handler.getnextUpdate();
+    return handler.getNextECLUpdate();
   }
 
   /**
@@ -106,7 +106,7 @@
   {
     MultiDomainServerState result = new MultiDomainServerState();
     // Initialize start state for  all running domains with empty state
-    Iterator<ReplicationServerDomain> rsdk = this.rs.getCacheIterator();
+    Iterator<ReplicationServerDomain> rsdk = rs.getDomainIterator();
     if (rsdk != null)
     {
       while (rsdk.hasNext())
@@ -116,7 +116,8 @@
         if (rsd.getBaseDn().compareToIgnoreCase(
             ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT)==0)
           continue;
-        result.update(rsd.getBaseDn(), rsd.getCLElligibleState());
+        result.update(rsd.getBaseDn(), rsd.getEligibleState(
+            rs.getEligibleCN()));
       }
     }
     return result;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationBackend.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationBackend.java
index b794d6a..51a69f0 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationBackend.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationBackend.java
@@ -351,7 +351,7 @@
     //This method only returns the number of actual change entries, the
     //domain and any baseDN entries are not counted.
     long retNum=0;
-    Iterator<ReplicationServerDomain> rcachei = server.getCacheIterator();
+    Iterator<ReplicationServerDomain> rcachei = server.getDomainIterator();
     if (rcachei != null)
     {
       while (rcachei.hasNext())
@@ -541,7 +541,7 @@
        Message message = ERR_REPLICATONBACKEND_EXPORT_LDIF_FAILED.get();
       throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,message);
     }
-    Iterator<ReplicationServerDomain> rsdi = server.getCacheIterator();
+    Iterator<ReplicationServerDomain> rsdi = server.getDomainIterator();
     if (rsdi != null)
     {
       while (rsdi.hasNext())
@@ -1358,7 +1358,7 @@
     }
 
     // Walk through all entries and send the ones that match.
-    Iterator<ReplicationServerDomain> rsdi = server.getCacheIterator();
+    Iterator<ReplicationServerDomain> rsdi = server.getDomainIterator();
     if (rsdi != null)
     {
       while (rsdi.hasNext())
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationDbEnv.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationDbEnv.java
index 4c6a4b3..cdaaa89 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationDbEnv.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationDbEnv.java
@@ -534,4 +534,27 @@
         {}
       }
     }
+
+    /**
+     * Get or create a db to manage integer change  number associated
+     * to multidomain server state.
+     * TODO:ECL how to manage compatibilty of this db with  new domains
+     * added or removed ?
+     * @return the retrieved or created db.
+     * @throws DatabaseException when a problem occurs.
+     */
+    public Database getOrCreateDraftCNDb()
+    throws DatabaseException
+    {
+      String stringId = "draftcndb";
+
+      // Opens the database for seqnum associated to this domain.
+      // Create it if it does not already exist.
+      DatabaseConfig dbConfig = new DatabaseConfig();
+      dbConfig.setAllowCreate(true);
+      dbConfig.setTransactional(true);
+      Database db = dbEnvironment.openDatabase(null, stringId, dbConfig);
+
+      return db;
+    }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationDraftCNKey.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationDraftCNKey.java
new file mode 100644
index 0000000..e4862d2
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationDraftCNKey.java
@@ -0,0 +1,68 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+
+import java.io.UnsupportedEncodingException;
+
+import com.sleepycat.je.DatabaseEntry;
+
+/**
+ * Superclass of DatabaseEntry.
+ * Useful to create ReplicationServer keys from sequence numbers.
+ */
+public class ReplicationDraftCNKey extends DatabaseEntry
+{
+  /**
+   * Creates a new ReplicationKey from the given draft ChangeNumber.
+   * @param draftCN The draft change number to use.
+   */
+  public ReplicationDraftCNKey(int draftCN)
+  {
+    try
+    {
+      String s = String.valueOf(draftCN);
+      int a = 16-s.length();
+      String sscn = new String("0000000000000000").substring(0, a) + s;
+      this.setData(sscn.getBytes("UTF-8"));
+    } catch (UnsupportedEncodingException e)
+    {
+      // Should never happens, UTF-8 is always supported
+      // TODO : add better logging
+    }
+  }
+
+  /**
+   * Getter for the draft change number associated with this key.
+   * @return the draft change number associated with this key.
+   */
+  public int getDraftCN()
+  {
+    String s = new String(this.getData());
+    int i = Integer.valueOf(s);
+    return i;
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationIterator.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationIterator.java
index 0fa4b09..6e0079b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationIterator.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationIterator.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Copyright 2006-2009 Sun Microsystems, Inc.
  */
 package org.opends.server.replication.server;
 
@@ -62,12 +62,6 @@
     {
       throw new Exception("no new change");
     }
-     if (this.next() == false)
-     {
-       cursor.close();
-       cursor = null;
-       throw new Exception("no new change");
-     }
   }
 
   /**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
index 77d068f..0b2ae52 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
@@ -42,6 +42,7 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -67,6 +68,7 @@
 import org.opends.server.core.networkgroups.NetworkGroup;
 import org.opends.server.loggers.LogLevel;
 import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.ChangeNumber;
 import org.opends.server.replication.common.ExternalChangeLogSession;
 import org.opends.server.replication.protocol.ProtocolSession;
 import org.opends.server.replication.protocol.ReplServerStartMsg;
@@ -164,6 +166,13 @@
   // the DS in DEGRADED_STATUS. If value is 0, status analyzer is disabled
   private int degradedStatusThreshold = 5000;
 
+  // The handler of the draft change numbers database, the database used to
+  // store the relation between a draft change number ('seqnum') and the
+  // associated cookie.
+  private DraftCNDbHandler draftCNDbHandler;
+  // The last value generated of the draft change number.
+  private int lastGeneratedDraftCN = 0;
+
   /**
    * The tracer object for the debug logger.
    */
@@ -1154,7 +1163,7 @@
    * Returns null if none.
    * @return the iterator.
    */
-  public Iterator<ReplicationServerDomain> getCacheIterator()
+  public Iterator<ReplicationServerDomain> getDomainIterator()
   {
     if (!baseDNs.isEmpty())
       return baseDNs.values().iterator();
@@ -1167,7 +1176,7 @@
    */
   public void clearDb()
   {
-    Iterator<ReplicationServerDomain> rcachei = getCacheIterator();
+    Iterator<ReplicationServerDomain> rcachei = getDomainIterator();
     if (rcachei != null)
     {
       while (rcachei.hasNext())
@@ -1445,4 +1454,134 @@
       return false;
     }
   }
+
+  private  ArrayList<String> excludedServiceIDs;
+  /**
+   * Excluded a list of domain from eligibility computation.
+   * @param excludedServiceIDs the provided list of serviceIDs excluded from
+   *                          the computation of eligibleCN.
+   */
+  public void disableEligibility(ArrayList<String> excludedServiceIDs)
+  {
+    this.excludedServiceIDs = excludedServiceIDs;
+  }
+
+  /**
+   * Returns the eligible CN cross domains - relies on the eligible CN from
+   * each domain.
+   * @return the cross domain eligible CN.
+   */
+  public ChangeNumber getEligibleCN()
+  {
+    String debugLog = "";
+
+    // traverse the domains and get the eligible CN from each domain
+    // store the oldest one as the cross domain eligible CN
+    ChangeNumber eligibleCN = null;
+    Iterator<ReplicationServerDomain> rsdi = this.getDomainIterator();
+    if (rsdi != null)
+    {
+      while (rsdi.hasNext())
+      {
+        ReplicationServerDomain domain = rsdi.next();
+
+        if (excludedServiceIDs.contains(domain.getBaseDn()))
+        {
+          continue;
+        }
+
+        ChangeNumber domainEligibleCN = domain.getEligibleCN();
+        String dates = "";
+        if (domainEligibleCN != null)
+        {
+          if ((eligibleCN == null) || (domainEligibleCN.older(eligibleCN)))
+          {
+            eligibleCN = domainEligibleCN;
+          }
+          dates = new Date(domainEligibleCN.getTime()).toString();
+        }
+        debugLog += "[dn=" + domain.getBaseDn()
+             + "] [eligibleCN=" + domainEligibleCN + ", " + dates + "]";
+      }
+    }
+
+    if (eligibleCN==null)
+    {
+      eligibleCN = new ChangeNumber(0,0,(short)0);
+    }
+
+    if (debugEnabled())
+      TRACER.debugInfo("In " + this +
+        " getEligibleCN() ends with " +
+        " the following domainEligibleCN for each domain :" + debugLog +
+        " thus CrossDomainEligibleCN=" + eligibleCN +
+        "  ts=" +
+        (eligibleCN!=null?
+        new Date(eligibleCN.getTime()).toString(): null));
+
+    return eligibleCN;
+  }
+
+  /**
+   * Get or create a handler on a Db on DraftCN for external changelog.
+   * @return the handler.
+   * @throws DirectoryException when needed.
+   */
+  public synchronized DraftCNDbHandler getDraftCNDbHandler()
+  throws DirectoryException
+  {
+    try
+    {
+      if (draftCNDbHandler == null)
+      {
+        draftCNDbHandler = new DraftCNDbHandler(this, this.dbEnv);
+        if (draftCNDbHandler == null)
+          return null;
+        this.lastGeneratedDraftCN = getLastDraftChangeNumber();
+      }
+      return draftCNDbHandler;
+    }
+    catch (Exception e)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      MessageBuilder mb = new MessageBuilder();
+      mb.append(ERR_DRAFT_CHANGENUMBER_DATABASE.get(""));
+      throw new DirectoryException(ResultCode.OPERATIONS_ERROR,
+          mb.toMessage(), e);
+    }
+  }
+
+  /**
+   * Get the value of the first draft change number, 0 when db is empty.
+   * @return the first value.
+   */
+  public int getFirstDraftChangeNumber()
+  {
+    int first=0;
+    if (draftCNDbHandler != null)
+      first = draftCNDbHandler.getFirstKey();
+    return first;
+  }
+
+  /**
+   * Get the value of the last draft change number, 0 when db is empty.
+   * @return the last value.
+   */
+  public int getLastDraftChangeNumber()
+  {
+    int last=0;
+    if (draftCNDbHandler != null)
+      last = draftCNDbHandler.getLastKey();
+    return last;
+  }
+
+  /**
+   * Generate a new Draft ChangeNumber.
+   * @return The generated Draft ChangeNUmber
+   */
+  synchronized public int getNewDraftCN()
+  {
+    return ++lastGeneratedDraftCN;
+  }
+
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
index 3bae821..4f844d4 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
@@ -63,6 +63,7 @@
 import org.opends.server.replication.common.ServerStatus;
 import org.opends.server.replication.common.StatusMachineEvent;
 import org.opends.server.replication.protocol.AckMsg;
+import org.opends.server.replication.protocol.ChangeTimeHeartbeatMsg;
 import org.opends.server.replication.protocol.ChangeStatusMsg;
 import org.opends.server.replication.protocol.ErrorMsg;
 import org.opends.server.replication.protocol.MonitorMsg;
@@ -75,9 +76,11 @@
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeBuilder;
 import org.opends.server.types.Attributes;
+import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.ResultCode;
 import org.opends.server.util.TimeThread;
+
 import com.sleepycat.je.DatabaseException;
 
 /**
@@ -173,6 +176,8 @@
   // every n number of treated assured messages
   private int assuredTimeoutTimerPurgeCounter = 0;
 
+  ServerState ctHeartbeatState = null;
+
   /**
    * Creates a new ReplicationServerDomain associated to the DN baseDn.
    *
@@ -360,8 +365,7 @@
         if ( (generationId>0) && (generationId != handler.getGenerationId()) )
         {
           if (debugEnabled())
-            TRACER.debugInfo("In RS " +
-              replicationServer.getServerId() +
+            TRACER.debugInfo("In " + this.getName() +
               " for dn " + baseDn + ", update " +
               update.getChangeNumber().toString() +
               " will not be sent to replication server " +
@@ -426,8 +430,7 @@
         if (debugEnabled())
         {
           if (dsStatus == ServerStatus.BAD_GEN_ID_STATUS)
-            TRACER.debugInfo("In RS " +
-              replicationServer.getServerId() +
+            TRACER.debugInfo("In " + this +
               " for dn " + baseDn + ", update " +
               update.getChangeNumber().toString() +
               " will not be sent to directory server " +
@@ -1024,10 +1027,9 @@
   {
       if (debugEnabled())
         TRACER.debugInfo(
-            "In RS " + this.replicationServer.getMonitorInstanceName() +
-            " domain=" + this +
-            " stopServer(SH)" + handler.getMonitorInstanceName() +
-          " " + stackTraceToSingleLineString(new Exception()));
+            "In " + this.replicationServer.getMonitorInstanceName() +
+            " domain=" + this + " stopServer() on the server handler " +
+            handler.getMonitorInstanceName());
     /*
      * We must prevent deadlock on replication server domain lock, when for
      * instance this code is called from dying ServerReader but also dying
@@ -1119,10 +1121,9 @@
   {
     if (debugEnabled())
       TRACER.debugInfo(
-          "In RS " + this.replicationServer.getMonitorInstanceName() +
-          " domain=" + this +
-          " stopServer(MH)" + handler.getMonitorInstanceName() +
-          " " + stackTraceToSingleLineString(new Exception()));
+          "In " + this.replicationServer.getMonitorInstanceName()
+          + " domain=" + this + " stopServer() on the message handler "
+          + handler.getMonitorInstanceName());
     /*
      * We must prevent deadlock on replication server domain lock, when for
      * instance this code is called from dying ServerReader but also dying
@@ -1363,7 +1364,40 @@
 
     try
     {
-      return handler.generateIterator(changeNumber);
+      ReplicationIterator it = handler.generateIterator(changeNumber);
+      if (it.next()==false)
+      {
+        it.releaseCursor();
+        throw new Exception("no new change");
+      }
+      return it;
+    } catch (Exception e)
+    {
+      return null;
+    }
+  }
+
+  /**
+   * Creates and returns an iterator.
+   * When the iterator is not used anymore, the caller MUST call the
+   * ReplicationIterator.releaseCursor() method to free the resources
+   * and locks used by the ReplicationIterator.
+   *
+   * @param serverId Identifier of the server for which the iterator is created.
+   * @param changeNumber Starting point for the iterator.
+   * @return the created ReplicationIterator. Null when no DB is available
+   * for the provided server Id.
+   */
+  public ReplicationIterator getIterator(short serverId,
+    ChangeNumber changeNumber)
+  {
+    DbHandler handler = sourceDbHandlers.get(serverId);
+    if (handler == null)
+      return null;
+    try
+    {
+      ReplicationIterator it = handler.generateIterator(changeNumber);
+      return it;
     } catch (Exception e)
     {
       return null;
@@ -1955,12 +1989,10 @@
     ResetGenerationIdMsg genIdMsg)
   {
     if (debugEnabled())
-    {
       TRACER.debugInfo(
-        "In RS " + getReplicationServer().getServerId() +
+        "In " + this +
         " Receiving ResetGenerationIdMsg from " + senderHandler.getServerId() +
         " for baseDn " + baseDn + ":\n" + genIdMsg);
-    }
 
     try
     {
@@ -1982,14 +2014,12 @@
     {
       // Order to take a gen id we already have, just ignore
       if (debugEnabled())
-      {
         TRACER.debugInfo(
-          "In RS " + getReplicationServer().getServerId()
+          "In " + this
           + " Reset generation id requested for baseDn " + baseDn
           + " but generation id was already " + this.generationId
           + ":\n" + genIdMsg);
       }
-    }
 
     // If we are the first replication server warned,
     // then forwards the reset message to the remote replication servers
@@ -2002,7 +2032,7 @@
         rsHandler.setGenerationId(newGenId);
         if (senderHandler.isDataServer())
         {
-          rsHandler.forwardGenerationIdToRS(genIdMsg);
+          rsHandler.send(genIdMsg);
         }
       } catch (IOException e)
       {
@@ -2158,7 +2188,7 @@
   }
 
   /**
-   * Clears the Db associated with that cache.
+   * Clears the Db associated with that domain.
    */
   public void clearDbs()
   {
@@ -2181,12 +2211,6 @@
         }
       }
       stopDbHandlers();
-
-      if (debugEnabled())
-        TRACER.debugInfo(
-          "In " + this.replicationServer.getMonitorInstanceName() +
-          " baseDN=" + baseDn +
-          " The source db handler has been cleared");
     }
     try
     {
@@ -2471,11 +2495,6 @@
    */
   public void receivesMonitorDataResponse(MonitorMsg msg)
   {
-    if (debugEnabled())
-      TRACER.debugInfo(
-        "In " + this.replicationServer.getMonitorInstanceName() +
-        "Receiving " + msg + " from " + msg.getsenderID());
-
     try
     {
       synchronized (monitorDataLock)
@@ -2543,7 +2562,7 @@
         {
           if (debugEnabled())
             TRACER.debugInfo(
-              "In " + this.replicationServer.getMonitorInstanceName() +
+              "In " + this +
               " baseDn=" + baseDn +
               " Processed msg from " + msg.getsenderID() +
               " New monitor data: " + wrkMonitorData.toString());
@@ -2819,24 +2838,29 @@
    * Return the state that contain for each server the time of eligibility.
    * @return the state.
    */
-  public ServerState getHeartbeatState()
+  public ServerState getChangeTimeHeartbeatState()
   {
-    // TODO:ECL Eligility must be supported
-    return this.getDbServerState();
+    if (ctHeartbeatState == null)
+    {
+      ctHeartbeatState = this.getDbServerState().duplicate();
+    }
+    return ctHeartbeatState;
   }
+
   /**
+   * TODO: code cleaning - remove this method.
    * Computes the change number eligible to the ECL.
    * @return null if the domain does not play in eligibility.
    */
-  public ChangeNumber computeEligibleCN()
+  public ChangeNumber computeEligibleCN2()
   {
-    ChangeNumber elligibleCN = null;
-    ServerState heartbeatState = getHeartbeatState();
+    ChangeNumber eligibleCN = null;
+    ServerState heartbeatState = getChangeTimeHeartbeatState();
 
     if (heartbeatState==null)
       return null;
 
-    // compute elligible CN
+    // compute eligible CN
     ServerState hbState = heartbeatState.duplicate();
 
     Iterator<Short> it = hbState.iterator();
@@ -2850,70 +2874,87 @@
       if (TimeThread.getTime()-storedCN.getTime()>2000)
       {
         if (debugEnabled())
-          TRACER.debugInfo(
-            "For RSD." + this.baseDn + " Server " + sid
+          TRACER.debugInfo("In " + this.getName() +
+            " Server " + sid
             + " is not considered for eligibility ... potentially down");
         continue;
       }
 
-      if ((elligibleCN == null) || (storedCN.older(elligibleCN)))
+      if ((eligibleCN == null) || (storedCN.older(eligibleCN)))
       {
-        elligibleCN = storedCN;
+        eligibleCN = storedCN;
       }
     }
 
     if (debugEnabled())
-      TRACER.debugInfo(
-        "For RSD." + this.baseDn + " ElligibleCN()=" + elligibleCN);
-    return elligibleCN;
+      TRACER.debugInfo("In " + this.getName() +
+        " computeEligibleCN() returns " + eligibleCN);
+    return eligibleCN;
   }
 
   /**
-   * Computes the eligible server state by minimizing the dbServerState and the
-   * elligibleCN.
+   * Computes the eligible server state for the domain.
+   * Consists in taking the most recent change from the dbServerState and the
+   * eligibleCN.
+   * @param eligibleCN The provided eligibleCN.
    * @return The computed eligible server state.
    */
-  public ServerState getCLElligibleState()
+  public ServerState getEligibleState(ChangeNumber eligibleCN)
   {
-    // ChangeNumber elligibleCN = computeEligibleCN();
-    ServerState res = new ServerState();
-    ServerState dbState = this.getDbServerState();
-    res = dbState;
+    ServerState result = new ServerState();
 
-    /* TODO:ECL Eligibility is not yet implemented
-    Iterator<Short> it = dbState.iterator();
-    while (it.hasNext())
+    ServerState dbState = this.getDbServerState();
+
+    result = dbState.duplicate();
+
+    if (eligibleCN != null)
     {
-      Short sid = it.next();
-      DbHandler h = sourceDbHandlers.get(sid);
-      ChangeNumber dbCN = dbState.getMaxChangeNumber(sid);
-      try
+      Iterator<Short> it = dbState.iterator();
+      while (it.hasNext())
       {
-        if ((elligibleCN!=null)&&(elligibleCN.older(dbCN)))
+        Short sid = it.next();
+        DbHandler h = sourceDbHandlers.get(sid);
+        ChangeNumber dbCN = dbState.getMaxChangeNumber(sid);
+        try
         {
-          // some CN exist in the db newer than elligible CN
-          ReplicationIterator ri = h.generateIterator(elligibleCN);
-          ChangeNumber newCN = ri.getCurrentCN();
-          res.update(newCN);
-          ri.releaseCursor();
+          if (eligibleCN.older(dbCN))
+          {
+            // some CN exist in the db newer than eligible CN
+            // let's get it
+            ReplicationIterator ri = h.generateIterator(eligibleCN);
+            try
+            {
+              if ((ri != null) && (ri.getChange()!=null))
+              {
+                ChangeNumber newCN = ri.getChange().getChangeNumber();
+                result.update(newCN);
+              }
+            }
+            finally
+            {
+              ri.releaseCursor();
+              ri = null;
+            }
+          }
+          else
+          {
+            // no CN exist in the db newer than elligible CN
+            result.update(dbCN);
+          }
         }
-        else
+        catch(Exception e)
         {
-          // no CN exist in the db newer than elligible CN
-          res.update(dbCN);
+          Message errMessage = ERR_WRITER_UNEXPECTED_EXCEPTION.get(
+              " " +  stackTraceToSingleLineString(e));
+          logError(errMessage);
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
         }
       }
-      catch(Exception e)
-      {
-        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-      }
     }
-    */
-
     if (debugEnabled())
-      TRACER.debugInfo("In " + this.getName()
-        + " getCLElligibleState returns:" + res);
-    return res;
+      TRACER.debugInfo("In " + this
+        + " getEligibleState() result is " + result);
+    return result;
   }
 
   /**
@@ -2930,4 +2971,206 @@
     }
     return domainStartState;
   }
+
+  /**
+   * Returns the eligibleCN for that domain - relies on the ChangeTimeHeartbeat
+   * state.
+   * For each DS, take the oldest CN from the changetime hearbeat state
+   * and from the changelog db last CN. Can be null.
+   * @return the eligible CN.
+   */
+  public ChangeNumber getEligibleCN()
+  {
+    ChangeNumber eligibleCN = null;
+
+    for (DbHandler db : sourceDbHandlers.values())
+    {
+      // Consider this producer (DS/db).
+      short sid = db.getServerId();
+
+      ChangeNumber changelogLastCN = db.getLastChange();
+      if (changelogLastCN != null)
+      {
+        if ((eligibleCN == null) || (changelogLastCN.newer(eligibleCN)))
+        {
+          eligibleCN = changelogLastCN;
+        }
+      }
+
+      ChangeNumber heartbeatLastDN =
+        getChangeTimeHeartbeatState().getMaxChangeNumber(sid);
+
+      if ((heartbeatLastDN != null) &&
+       ((eligibleCN == null) || (heartbeatLastDN.newer(eligibleCN))))
+      {
+        eligibleCN = heartbeatLastDN;
+      }
+    }
+
+    if (debugEnabled())
+      TRACER.debugInfo(
+        "In " + this.getName() + " getEligibleCN() returns result ="
+        + eligibleCN);
+    return eligibleCN;
+  }
+
+  /**
+   * Processes a ChangeTimeHeartbeatMsg received, by storing the CN (timestamp)
+   * value received, and forwarding the message to the other RSes.
+   * @param senderHandler The handler for the server that sent the heartbeat.
+   * @param msg The message to process.
+   */
+  public void processChangeTimeHeartbeatMsg(ServerHandler senderHandler,
+      ChangeTimeHeartbeatMsg msg )
+  {
+    try
+    {
+      // Acquire lock on domain (see more details in comment of start() method
+      // of ServerHandler)
+      lock();
+    } catch (InterruptedException ex)
+    {
+      // Try doing job anyway...
+    }
+
+    try
+    {
+      storeReceivedCTHeartbeat(msg.getChangeNumber());
+
+      // If we are the first replication server warned,
+      // then forwards the reset message to the remote replication servers
+      for (ReplicationServerHandler rsHandler : replicationServers.values())
+      {
+        try
+        {
+          // After we'll have sent the message , the remote RS will adopt
+          // the new genId
+          if (senderHandler.isDataServer())
+          {
+            rsHandler.send(msg);
+          }
+        } catch (IOException e)
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          logError(ERR_CHANGELOG_ERROR_SENDING_MSG.get(rsHandler.getName()));
+          stopServer(rsHandler);
+        }
+      }
+    }
+    finally
+    {
+      release();
+    }
+  }
+
+  /**
+   * Store a change time value received from a data server.
+   * @param cn The provided change time.
+   */
+  public void storeReceivedCTHeartbeat(ChangeNumber cn)
+  {
+    // TODO:May be we can spare processing by only storing CN (timestamp)
+    // instead of a server state.
+    getChangeTimeHeartbeatState().update(cn);
+
+    /*
+    if (debugEnabled())
+    {
+      Set<String> ss = ctHeartbeatState.toStringSet();
+      String dss = "";
+      for (String s : ss)
+      {
+        dss = dss + " \\ " + s;
+      }
+      TRACER.debugInfo("In " + this.getName() + " " + dss);
+    }
+    */
+  }
+
+  /**
+   * This methods count the changes, server by server :
+   * - from a start point (cn taken from the provided startState)
+   * - to an end point (the provided endCN).
+   * @param startState The provided start server state.
+   * @param endCN The provided end change number.
+   * @return The number of changes between startState and endCN.
+   */
+  public long getEligibleCount(ServerState startState, ChangeNumber endCN)
+  {
+    long res = 0;
+    ReplicationIterator ri=null;
+
+    // Parses the dbState of the domain , server by server
+    ServerState dbState = this.getDbServerState();
+    Iterator<Short> it = dbState.iterator();
+    while (it.hasNext())
+    {
+      // for each server
+      Short sid = it.next();
+      DbHandler h = sourceDbHandlers.get(sid);
+
+      try
+      {
+        // Set on the change related to the startState
+        ChangeNumber startCN = null;
+        try
+        {
+          ri = h.generateIterator(startState.getMaxChangeNumber(sid));
+          startCN = ri.getChange().getChangeNumber();
+        }
+        catch(Exception e)
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          // no change found (purge from CL)
+          startCN = null;
+        }
+        finally
+        {
+          if (ri!=null)
+          {
+            ri.releaseCursor();
+            ri = null;
+          }
+        }
+
+        if (startCN != null)
+        {
+          // Set on the change related to the endCN
+          ChangeNumber upperCN;
+          try
+          {
+            // Build a changenumber for this very server, with the timestamp
+            // of the endCN
+            ChangeNumber f = new ChangeNumber(endCN.getTime(), 0, sid);
+            ri = h.generateIterator(f);
+            upperCN = ri.getChange().getChangeNumber();
+          }
+          catch(Exception e)
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            // no new change
+            upperCN = h.getLastChange();
+          }
+          finally
+          {
+            if (ri!=null)
+            {
+              ri.releaseCursor();
+              ri = null;
+            }
+          }
+
+          long diff = upperCN.getSeqnum() - startCN.getSeqnum() + 1;
+
+          res += diff;
+        }
+        // TODO:ECL We should compute if changenumber.seqnum has turned !
+      }
+      catch(Exception e)
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+    }
+    return res;
+  }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java
index 019cbf5..d71d11f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java
@@ -783,4 +783,19 @@
   {
     return serverAddressURL;
   }
+
+  /**
+   * Sends a message containing a generationId to a peer server.
+   * The peer is expected to be a replication server.
+   *
+   * @param  msg         The GenerationIdMessage message to be sent.
+   * @throws IOException When it occurs while sending the message,
+   *
+   */
+  public void forwardReplicationMsg(ReplicationMsg msg)
+    throws IOException
+  {
+    session.publish(msg);
+  }
+
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java
index 384dcae..90e593b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java
@@ -46,13 +46,13 @@
 import org.opends.server.replication.common.ChangeNumber;
 import org.opends.server.replication.common.RSInfo;
 import org.opends.server.replication.common.ServerStatus;
+import org.opends.server.replication.protocol.ReplicationMsg;
 import org.opends.server.replication.protocol.AckMsg;
 import org.opends.server.replication.protocol.ErrorMsg;
 import org.opends.server.replication.protocol.HeartbeatThread;
 import org.opends.server.replication.protocol.ProtocolSession;
 import org.opends.server.replication.protocol.ProtocolVersion;
 import org.opends.server.replication.protocol.ReplServerStartMsg;
-import org.opends.server.replication.protocol.ResetGenerationIdMsg;
 import org.opends.server.replication.protocol.RoutableMsg;
 import org.opends.server.replication.protocol.StartECLSessionMsg;
 import org.opends.server.replication.protocol.StartMsg;
@@ -389,14 +389,12 @@
   }
 
   /**
-   * Sends a message containing a generationId to a peer server.
-   * The peer is expected to be a replication server.
-   *
-   * @param  msg         The GenerationIdMessage message to be sent.
+   * Sends a message.
+   * @param  msg         The message to be sent.
    * @throws IOException When it occurs while sending the message,
    *
    */
-  public void forwardGenerationIdToRS(ResetGenerationIdMsg msg)
+  public void send(ReplicationMsg msg)
   throws IOException
   {
     session.publish(msg);
@@ -1352,5 +1350,4 @@
           inStartECLSessionMsg.toString());
     }
   }
-
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java
index c128db2..0bae9e6 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java
@@ -52,6 +52,7 @@
 import org.opends.server.replication.protocol.TopologyMsg;
 import org.opends.server.replication.protocol.MonitorMsg;
 import org.opends.server.replication.protocol.MonitorRequestMsg;
+import org.opends.server.replication.protocol.ChangeTimeHeartbeatMsg;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.common.ServerStatus;
 import org.opends.server.replication.protocol.ChangeStatusMsg;
@@ -127,7 +128,8 @@
 
           if (debugEnabled())
           {
-            TRACER.debugInfo(this.getName() + " receives " + msg);
+            TRACER.debugInfo("In " + replicationServerDomain + " " +
+                getName() + " receives " + msg);
           }
 
           if (msg instanceof AckMsg)
@@ -282,6 +284,11 @@
           {
             MonitorMsg replServerMonitorMsg = (MonitorMsg) msg;
             handler.process(replServerMonitorMsg);
+          } else if (msg instanceof ChangeTimeHeartbeatMsg)
+          {
+            ChangeTimeHeartbeatMsg cthbMsg = (ChangeTimeHeartbeatMsg) msg;
+            replicationServerDomain.processChangeTimeHeartbeatMsg(handler,
+                cthbMsg);
           } else if (msg == null)
           {
             /*
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/service/CTHeartbeatPublisherThread.java b/opendj-sdk/opends/src/server/org/opends/server/replication/service/CTHeartbeatPublisherThread.java
new file mode 100644
index 0000000..082f67b
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/service/CTHeartbeatPublisherThread.java
@@ -0,0 +1,185 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.server.replication.service;
+
+import org.opends.server.api.DirectoryThread;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.protocol.ChangeTimeHeartbeatMsg;
+import org.opends.server.replication.protocol.ProtocolSession;
+import org.opends.server.util.TimeThread;
+
+import java.io.IOException;
+
+/**
+ * This thread publishes a heartbeat message on a given protocol session at
+ * regular intervals when there are no other replication messages being
+ * published.
+ */
+public class CTHeartbeatPublisherThread extends DirectoryThread
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  /**
+   * For test purposes only to simulate loss of heartbeats.
+   */
+  static private boolean heartbeatsDisabled = false;
+
+  /**
+   * The session on which heartbeats are to be sent.
+   */
+  private ProtocolSession session;
+
+  /**
+   * The time in milliseconds between heartbeats.
+   */
+  private long heartbeatInterval;
+  private short serverId;
+
+  /**
+   * Set this to stop the thread.
+   */
+  private Boolean shutdown = false;
+  private final Object shutdown_lock = new Object();
+
+  /**
+   * Create a heartbeat thread.
+   * @param threadName The name of the heartbeat thread.
+   * @param session The session on which heartbeats are to be sent.
+   * @param heartbeatInterval The interval between heartbeats sent
+   *                          (in milliseconds).
+   * @param serverId The serverId of the sender domain.
+   */
+  public CTHeartbeatPublisherThread(String threadName, ProtocolSession session,
+                  long heartbeatInterval, short serverId)
+  {
+    super(threadName);
+    this.session = session;
+    this.heartbeatInterval = heartbeatInterval;
+    this.serverId = serverId;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void run()
+  {
+    try
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo(getName() + " is starting, interval is %d",
+                  heartbeatInterval);
+      }
+
+      while (!shutdown)
+      {
+        long now = System.currentTimeMillis();
+        ChangeTimeHeartbeatMsg ctHeartbeatMsg =
+         new ChangeTimeHeartbeatMsg(
+             new ChangeNumber(TimeThread.getTime(),0, serverId));
+
+        if (now > session.getLastPublishTime() + heartbeatInterval)
+        {
+          if (!heartbeatsDisabled)
+          {
+            session.publish(ctHeartbeatMsg);
+          }
+        }
+
+        try
+        {
+          long sleepTime = session.getLastPublishTime() +
+              heartbeatInterval - now;
+          if (sleepTime <= 0)
+          {
+            sleepTime = heartbeatInterval;
+          }
+
+          synchronized (shutdown_lock)
+          {
+            if (!shutdown)
+            {
+              shutdown_lock.wait(sleepTime);
+            }
+          }
+        }
+        catch (InterruptedException e)
+        {
+          // Keep looping.
+        }
+      }
+    }
+    catch (IOException e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo(getName() + "could not send a heartbeat." +
+            e.getMessage() + e.toString());
+      }
+      // This will be caught in another thread.
+    }
+    finally
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo(getName()+" is exiting.");
+      }
+    }
+  }
+
+
+  /**
+   * Call this method to stop the thread.
+   * This method is blocking until the thread has stopped.
+   */
+  public void shutdown()
+  {
+    synchronized (shutdown_lock)
+    {
+      shutdown = true;
+      shutdown_lock.notifyAll();
+    }
+  }
+
+
+  /**
+   * For testing purposes only to simulate loss of heartbeats.
+   * @param heartbeatsDisabled Set true to prevent heartbeats from being sent.
+   */
+  public static void setHeartbeatsDisabled(boolean heartbeatsDisabled)
+  {
+    CTHeartbeatPublisherThread.heartbeatsDisabled = heartbeatsDisabled;
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java b/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java
index e904311..fbc1776 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java
@@ -116,8 +116,8 @@
   // performPhaseOneHandshake method.
   private String tmpReadableServerName = null;
   /**
-   * The time in milliseconds between heartbeats from the replication
-   * server.  Zero means heartbeats are off.
+   * The expected duration in milliseconds between heartbeats received
+   * from the replication server.  Zero means heartbeats are off.
    */
   private long heartbeatInterval = 0;
   /**
@@ -142,6 +142,16 @@
   // Same group id poller thread
   private SameGroupIdPoller sameGroupIdPoller = null;
 
+  /**
+   * The thread that publishes messages to the RS containing the current
+   * change time of this DS.
+   */
+  private CTHeartbeatPublisherThread ctHeartbeatPublisherThread = null;
+  /**
+   * The expected period in milliseconds between these messages are sent
+   * to the replication server.  Zero means heartbeats are off.
+   */
+  private long changeTimeHeartbeatSendInterval = 0;
   /*
    * Properties for the last topology info received from the network.
    */
@@ -159,24 +169,27 @@
    *
    * @param replicationDomain The replication domain that is creating us.
    * @param state The ServerState that should be used by this broker
-   *              when negotiating the session with the replicationServer.
+   *        when negotiating the session with the replicationServer.
    * @param baseDn The base DN that should be used by this broker
-   *              when negotiating the session with the replicationServer.
+   *        when negotiating the session with the replicationServer.
    * @param serverId The server ID that should be used by this broker
-   *              when negotiating the session with the replicationServer.
+   *        when negotiating the session with the replicationServer.
    * @param window The size of the send and receive window to use.
-   * @param heartbeatInterval The interval between heartbeats requested of the
-   * replicationServer, or zero if no heartbeats are requested.
-   *
    * @param generationId The generationId for the server associated to the
    * provided serverId and for the domain associated to the provided baseDN.
+   * @param heartbeatInterval The interval (in ms) between heartbeats requested
+   *        from the replicationServer, or zero if no heartbeats are requested.
    * @param replSessionSecurity The session security configuration.
    * @param groupId The group id of our domain.
+   * @param changeTimeHeartbeatInterval The interval (in ms) between Change
+   *        time  heartbeats are sent to the RS,
+   *        or zero if no CN heartbeat shoud be sent.
    */
   public ReplicationBroker(ReplicationDomain replicationDomain,
     ServerState state, String baseDn, short serverId, int window,
     long generationId, long heartbeatInterval,
-    ReplSessionSecurity replSessionSecurity, byte groupId)
+    ReplSessionSecurity replSessionSecurity, byte groupId,
+    long changeTimeHeartbeatInterval)
   {
     this.domain = replicationDomain;
     this.baseDn = baseDn;
@@ -190,6 +203,7 @@
     this.maxRcvWindow = window;
     this.maxRcvWindow = window;
     this.halfRcvWindow = window /2;
+    this.changeTimeHeartbeatSendInterval = changeTimeHeartbeatInterval;
   }
 
   /**
@@ -392,7 +406,8 @@
 
     // Stop any existing poller and heartbeat monitor from a previous session.
     stopSameGroupIdPoller();
-    stopHeartBeat();
+    stopRSHeartBeatMonitoring();
+    stopChangeTimeHeartBeatPublishing();
 
     boolean newServerWithSameGroupId = false;
     synchronized (connectPhaseLock)
@@ -508,7 +523,8 @@
                  logError(message);
                  startSameGroupIdPoller();
                 }
-                startHeartBeat();
+                startRSHeartBeatMonitoring();
+                startChangeTimeHeartBeatPublishing();
               } else
               {
                 // Detected new RS with our group id: log disconnection to
@@ -1025,7 +1041,7 @@
       // Send our Start Session
       StartECLSessionMsg startECLSessionMsg = null;
       startECLSessionMsg = new StartECLSessionMsg();
-      startECLSessionMsg.setOperationId(Short.toString(serverId));
+      startECLSessionMsg.setOperationId("-1");
       session.publish(startECLSessionMsg);
 
       /* FIXME:ECL In the handshake phase two, should RS send back a topo msg ?
@@ -1428,7 +1444,7 @@
   /**
    * Start the heartbeat monitor thread.
    */
-  private void startHeartBeat()
+  private void startRSHeartBeatMonitoring()
   {
     // Start a heartbeat monitor thread.
     if (heartbeatInterval > 0)
@@ -1467,7 +1483,7 @@
   /**
    * Stop the heartbeat monitor thread.
    */
-  void stopHeartBeat()
+  void stopRSHeartBeatMonitoring()
   {
     if (heartbeatMonitor != null)
     {
@@ -1753,7 +1769,8 @@
         + " domain " + baseDn);
     }
     stopSameGroupIdPoller();
-    stopHeartBeat();
+    stopRSHeartBeatMonitoring();
+    stopChangeTimeHeartBeatPublishing();
     replicationServer = "stopped";
     shutdown = true;
     connected = false;
@@ -2156,4 +2173,38 @@
   {
     return connectionError;
   }
+
+  /**
+   * Starts publishing to the RS the current timestamp used in this server.
+   */
+  public void startChangeTimeHeartBeatPublishing()
+  {
+    // Start a CN heartbeat thread.
+    if (changeTimeHeartbeatSendInterval > 0)
+    {
+      ctHeartbeatPublisherThread =
+        new CTHeartbeatPublisherThread(
+            "Replication CN Heartbeat Thread started for " +
+            baseDn + " with " + getReplicationServer(),
+            session, changeTimeHeartbeatSendInterval, serverId);
+      ctHeartbeatPublisherThread.start();
+    }
+    else
+    {
+      TRACER.debugInfo(this +
+          " is not configured to send CN heartbeat interval");
+    }
+  }
+
+  /**
+   * Stops publishing to the RS the current timestamp used in this server.
+   */
+  public void stopChangeTimeHeartBeatPublishing()
+  {
+    if (ctHeartbeatPublisherThread != null)
+    {
+      ctHeartbeatPublisherThread.shutdown();
+      ctHeartbeatPublisherThread = null;
+    }
+  }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationDomain.java b/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationDomain.java
index 2272923..0c1e208 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationDomain.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationDomain.java
@@ -2292,13 +2292,16 @@
    * @param heartbeatInterval    The heartbeatInterval that should be used
    *                             to check the availability of the replication
    *                             servers.
+   * @param changetimeHeartbeatInterval  The interval used to send change
+   *                             time heartbeat to the replication server.
    *
    * @throws ConfigException     If the DirectoryServer configuration was
    *                             incorrect.
    */
   public void startPublishService(
       Collection<String> replicationServers, int window,
-      long heartbeatInterval) throws ConfigException
+      long heartbeatInterval, long changetimeHeartbeatInterval)
+  throws ConfigException
   {
     if (broker == null)
     {
@@ -2311,7 +2314,8 @@
           getGenerationID(),
           heartbeatInterval,
           new ReplSessionSecurity(),
-          getGroupId());
+          getGroupId(),
+          changetimeHeartbeatInterval);
 
       broker.start(replicationServers);
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
index 64be4c0..95bb790 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
@@ -37,12 +37,12 @@
 
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.TimeZone;
@@ -81,7 +81,6 @@
 import org.opends.server.replication.protocol.StartECLSessionMsg;
 import org.opends.server.replication.protocol.UpdateMsg;
 import org.opends.server.replication.server.ReplicationServer;
-import org.opends.server.replication.service.ReplicationDomain;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValue;
@@ -94,6 +93,7 @@
 import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.Entry;
+import org.opends.server.types.FilterType;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ObjectClass;
 import org.opends.server.types.Privilege;
@@ -107,7 +107,6 @@
 import org.opends.server.types.operation.SearchReferenceSearchOperation;
 import org.opends.server.util.Base64;
 import org.opends.server.util.ServerConstants;
-import org.opends.server.util.TimeThread;
 
 
 
@@ -136,22 +135,12 @@
   // The associated DN.
   private DN rootBaseDN;
 
-  // The cookie received in the ECL request control coming along
-  // with the request.
-  MultiDomainServerState requestCookie = null;
-
   /**
    * The replication server in which the search on ECL is to be performed.
    */
   protected ReplicationServer replicationServer;
 
   /**
-   * Indicates whether we should actually process the search.  This should
-   * only be false if it's a persistent search with changesOnly=true.
-   */
-  protected boolean changesOnly;
-
-  /**
    * The client connection for the search operation.
    */
   protected ClientConnection clientConnection;
@@ -177,6 +166,7 @@
   private HashSet<String> supportedControls;
 
   // The set of supported features for this WE
+  // TODO: any special feature to be implemented for an ECL search operation ?
   private HashSet<String> supportedFeatures;
 
   String privateDomainsBaseDN;
@@ -202,11 +192,12 @@
     ObjectClass topOC = DirectoryServer.getObjectClass(OC_TOP, true);
     eclObjectClasses.put(topOC, OC_TOP);
     ObjectClass eclEntryOC = DirectoryServer.getObjectClass(OC_CHANGELOG_ENTRY,
-                                                           true);
+        true);
     eclObjectClasses.put(eclEntryOC, OC_CHANGELOG_ENTRY);
 
 
     // Define an empty sets for the supported controls and features.
+    // FIXME:ECL Decide if ServerSideControl and VLV are supported
     supportedControls = new HashSet<String>(0);
     supportedControls.add(ServerConstants.OID_SERVER_SIDE_SORT_REQUEST_CONTROL);
     supportedControls.add(ServerConstants.OID_VLV_REQUEST_CONTROL);
@@ -226,57 +217,79 @@
    *           if this operation should be cancelled
    */
   public void processECLSearch(ECLWorkflowElement wfe)
-      throws CanceledOperationException
+  throws CanceledOperationException
   {
     boolean executePostOpPlugins = false;
-    this.replicationServer = wfe.getReplicationServer();
-
-    clientConnection = getClientConnection();
 
     // Get the plugin config manager that will be used for invoking plugins.
     PluginConfigManager pluginConfigManager =
       DirectoryServer.getPluginConfigManager();
-    changesOnly = false;
 
     // Check for a request to cancel this operation.
     checkIfCanceled(false);
 
-    // Create a labeled block of code that we can break out of if a problem is
-    // detected.
-searchProcessing:
+    searchProcessing:
     {
+      replicationServer  = wfe.getReplicationServer();
+      clientConnection   = getClientConnection();
+      startECLSessionMsg = new StartECLSessionMsg();
+
+      // Set default behavior as "from draft change number".
+      // "from cookie" is set only when cookie is provided.
+      startECLSessionMsg.setECLRequestType(
+          StartECLSessionMsg.REQUEST_TYPE_FROM_DRAFT_CHANGE_NUMBER);
+
+      // Set a string operationid that will help correlate any error message
+      // logged for this operation with the 'real' client operation.
+      startECLSessionMsg.setOperationId(this.toString());
+
+      // Set a list of excluded domains (also exclude 'cn=changelog' itself)
+      ArrayList<String> excludedDomains =
+        MultimasterReplication.getPrivateDomains();
+      if (!excludedDomains.contains(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT))
+        excludedDomains.add(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
+      startECLSessionMsg.setExcludedDNs(excludedDomains);
+
+      // Test existence of the RS - normally should always be here
+      if (replicationServer == null)
+      {
+        setResultCode(ResultCode.OPERATIONS_ERROR);
+        appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(
+            String.valueOf(baseDN)));
+        break searchProcessing;
+      }
+
       // 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
       // search processing.
       baseDN = getBaseDN();
       filter = getFilter();
-
       if ((baseDN == null) || (filter == null)){
         break searchProcessing;
       }
 
-      // Check to see if there are any controls in the request.  If so, then
-      // see if there is any special processing required.
+      // Analyse controls - including the cookie control
       try
       {
-        this.requestCookie = null;
         handleRequestControls();
-        if (this.requestCookie == null)
-        {
-          setResponseData(new DirectoryException(
-              ResultCode.OPERATIONS_ERROR,
-              Message.raw(Category.SYNC, Severity.FATAL_ERROR,
-                  "Cookie control expected")));
-          break searchProcessing;
-        }
       }
       catch (DirectoryException de)
       {
         if (debugEnabled())
-        {
           TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
+        setResponseData(de);
+        break searchProcessing;
+      }
 
+      // Process filter - extract draft change number (seqnum) conditions
+      try
+      {
+        evaluateFilter(startECLSessionMsg, this.getFilter());
+      }
+      catch (DirectoryException de)
+      {
+        if (debugEnabled())
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
         setResponseData(de);
         break searchProcessing;
       }
@@ -287,7 +300,7 @@
       // Invoke the pre-operation search plugins.
       executePostOpPlugins = true;
       PluginResult.PreOperation preOpResult =
-          pluginConfigManager.invokePreOperationSearchPlugins(this);
+        pluginConfigManager.invokePreOperationSearchPlugins(this);
       if (!preOpResult.continueProcessing())
       {
         setResultCode(preOpResult.getResultCode());
@@ -297,26 +310,12 @@
         break searchProcessing;
       }
 
-
       // Check for a request to cancel this operation.
       checkIfCanceled(false);
 
-
-      // Test existence of the RS
-      if (replicationServer == null)
-      {
-        setResultCode(ResultCode.OPERATIONS_ERROR);
-        appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(
-                                String.valueOf(baseDN)));
-        break searchProcessing;
-      }
-
-
-      // We'll set the result code to "success".  If a problem occurs, then it
-      // will be overwritten.
+      // Be optimistic by default.
       setResultCode(ResultCode.SUCCESS);
 
-
       // If there's a persistent search, then register it with the server.
       if (persistentSearch != null)
       {
@@ -332,9 +331,7 @@
       catch (DirectoryException de)
       {
         if (debugEnabled())
-        {
           TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
 
         setResponseData(de);
 
@@ -343,7 +340,6 @@
           persistentSearch.cancel();
           setSendResponse(true);
         }
-
         break searchProcessing;
       }
       catch (CanceledOperationException coe)
@@ -353,31 +349,25 @@
           persistentSearch.cancel();
           setSendResponse(true);
         }
-
         throw coe;
       }
       catch (Exception e)
       {
         if (debugEnabled())
-        {
           TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
 
         setResultCode(DirectoryServer.getServerErrorResultCode());
         appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION.get(
-                                getExceptionMessage(e)));
-
+            getExceptionMessage(e)));
         if (persistentSearch != null)
         {
           persistentSearch.cancel();
           setSendResponse(true);
         }
-
         break searchProcessing;
       }
     }
 
-
     // Check for a request to cancel this operation.
     checkIfCanceled(false);
 
@@ -385,7 +375,7 @@
     if (executePostOpPlugins)
     {
       PluginResult.PostOperation postOpResult =
-           pluginConfigManager.invokePostOperationSearchPlugins(this);
+        pluginConfigManager.invokePostOperationSearchPlugins(this);
       if (!postOpResult.continueProcessing())
       {
         setResultCode(postOpResult.getResultCode());
@@ -398,13 +388,13 @@
 
 
   /**
-   * Handles any controls contained in the request.
+   * Handles any controls contained in the request - including the cookie ctrl.
    *
    * @throws  DirectoryException  If there is a problem with any of the request
    *                              controls.
    */
   protected void handleRequestControls()
-          throws DirectoryException
+  throws DirectoryException
   {
     List<Control> requestControls  = getRequestControls();
     if ((requestControls != null) && (! requestControls.isEmpty()))
@@ -414,22 +404,28 @@
         Control c   = requestControls.get(i);
         String  oid = c.getOID();
         if (! AccessControlConfigManager.getInstance().
-                   getAccessControlHandler().isAllowed(baseDN, this, c))
+            getAccessControlHandler().isAllowed(baseDN, this, c))
         {
           throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
+              ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
         }
 
         if (oid.equals(OID_ECL_COOKIE_EXCHANGE_CONTROL))
         {
           ExternalChangelogRequestControl eclControl =
             getRequestControl(ExternalChangelogRequestControl.DECODER);
-          this.requestCookie = eclControl.getCookie();
+          MultiDomainServerState cookie = eclControl.getCookie();
+          if (cookie!=null)
+          {
+            startECLSessionMsg.setECLRequestType(
+                StartECLSessionMsg.REQUEST_TYPE_FROM_COOKIE);
+            startECLSessionMsg.setCrossDomainServerState(cookie.toString());
+          }
         }
         else if (oid.equals(OID_LDAP_ASSERTION))
         {
           LDAPAssertionRequestControl assertControl =
-                getRequestControl(LDAPAssertionRequestControl.DECODER);
+            getRequestControl(LDAPAssertionRequestControl.DECODER);
 
           try
           {
@@ -449,20 +445,20 @@
               }
 
               throw new DirectoryException(de.getResultCode(),
-                             ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(
-                                  de.getMessageObject()));
+                  ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(
+                      de.getMessageObject()));
             }
 
             if (entry == null)
             {
               throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
-                             ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
+                  ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
             }
 
             if (! assertionFilter.matchesEntry(entry))
             {
               throw new DirectoryException(ResultCode.ASSERTION_FAILED,
-                                           ERR_SEARCH_ASSERTION_FAILED.get());
+                  ERR_SEARCH_ASSERTION_FAILED.get());
             }
           }
           catch (DirectoryException de)
@@ -478,8 +474,8 @@
             }
 
             throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
-                           ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
-                                de.getMessageObject()), de);
+                ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
+                    de.getMessageObject()), de);
           }
         }
         else if (oid.equals(OID_PROXIED_AUTH_V1))
@@ -489,11 +485,11 @@
           if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
           {
             throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
-                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+                ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
           }
 
           ProxiedAuthV1Control proxyControl =
-              getRequestControl(ProxiedAuthV1Control.DECODER);
+            getRequestControl(ProxiedAuthV1Control.DECODER);
 
           Entry authorizationEntry = proxyControl.getAuthorizationEntry();
           setAuthorizationEntry(authorizationEntry);
@@ -513,11 +509,11 @@
           if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
           {
             throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
-                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+                ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
           }
 
           ProxiedAuthV2Control proxyControl =
-              getRequestControl(ProxiedAuthV2Control.DECODER);
+            getRequestControl(ProxiedAuthV2Control.DECODER);
 
           Entry authorizationEntry = proxyControl.getAuthorizationEntry();
           setAuthorizationEntry(authorizationEntry);
@@ -536,15 +532,17 @@
             getRequestControl(PersistentSearchControl.DECODER);
 
           persistentSearch = new PersistentSearch(this,
-                                      psearchControl.getChangeTypes(),
-                                      psearchControl.getReturnECs());
+              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())
-          {
-            changesOnly = true;
-          }
+          if (!psearchControl.getChangesOnly())
+            startECLSessionMsg.setPersistent(
+                StartECLSessionMsg.PERSISTENT);
+          else
+            startECLSessionMsg.setPersistent(
+                StartECLSessionMsg.PERSISTENT_CHANGES_ONLY);
         }
         else if (oid.equals(OID_LDAP_SUBENTRIES))
         {
@@ -553,7 +551,7 @@
         else if (oid.equals(OID_MATCHED_VALUES))
         {
           MatchedValuesControl matchedValuesControl =
-                getRequestControl(MatchedValuesControl.DECODER);
+            getRequestControl(MatchedValuesControl.DECODER);
           setMatchedValuesControl(matchedValuesControl);
         }
         else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
@@ -569,20 +567,19 @@
           setVirtualAttributesOnly(true);
         }
         else if (oid.equals(OID_GET_EFFECTIVE_RIGHTS) &&
-          DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS))
+            DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS))
         {
           // Do nothing here and let AciHandler deal with it.
         }
 
-        // NYI -- Add support for additional controls.
-
+        // TODO: Add support for additional controls, including VLV
         else if (c.isCritical())
         {
           if ((replicationServer == null) || (! supportsControl(oid)))
           {
             throw new DirectoryException(
-                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
-                           ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
+                ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
+                ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
           }
         }
       }
@@ -592,35 +589,12 @@
   private void processSearch()
   throws DirectoryException, CanceledOperationException
   {
-    startECLSessionMsg = new StartECLSessionMsg();
-    startECLSessionMsg.setECLRequestType(
-        StartECLSessionMsg.REQUEST_TYPE_FROM_COOKIE);
-    startECLSessionMsg.setChangeNumber(
-        new ChangeNumber(TimeThread.getTime(),(short)0, (short)0));
-    startECLSessionMsg.setCrossDomainServerState(requestCookie.toString());
+    if (debugEnabled())
+      TRACER.debugInfo(
+        " processSearch toString=[" + toString() + "] opid=["
+        + startECLSessionMsg.getOperationId() + "]");
 
-    if (persistentSearch==null)
-      startECLSessionMsg.setPersistent(StartECLSessionMsg.NON_PERSISTENT);
-    else
-      if (!changesOnly)
-        startECLSessionMsg.setPersistent(StartECLSessionMsg.PERSISTENT);
-      else
-        startECLSessionMsg.setPersistent(
-            StartECLSessionMsg.PERSISTENT_CHANGES_ONLY);
-
-    startECLSessionMsg.setFirstDraftChangeNumber(0);
-    startECLSessionMsg.setLastDraftChangeNumber(0);
-
-    // Help correlate with access log with the format: "conn=x op=y msgID=z"
-    startECLSessionMsg.setOperationId(
-      "conn="+String.valueOf(this.getConnectionID())
-      + " op="+String.valueOf(this.getOperationID())
-      + " msgID="+String.valueOf(getOperationID()));
-
-    startECLSessionMsg.setExcludedDNs(
-        MultimasterReplication.getPrivateDomains());
-
-    // Start session
+    // Start a specific ECL session
     eclSession = replicationServer.createECLSession(startECLSessionMsg);
 
     if (!getScope().equals(SearchScope.SINGLE_LEVEL))
@@ -718,9 +692,9 @@
   private boolean matchFilter(Entry entry)
   throws DirectoryException
   {
-    boolean ms = entry.matchesBaseAndScope(getBaseDN(), getScope());
-    boolean mf = getFilter().matchesEntry(entry);
-    return (ms && mf);
+    boolean baseScopeMatch = entry.matchesBaseAndScope(getBaseDN(), getScope());
+    boolean filterMatch = getFilter().matchesEntry(entry);
+    return (baseScopeMatch && filterMatch);
   }
 
   /**
@@ -755,95 +729,99 @@
           null, // real time current entry
           null, // real time attrs names
           null, // hist entry attributes
-          -1,    // TODO:ECL G Good changelog draft compat. addMsg.getSeqnum()
+          eclmsg.getDraftChangeNumber(),
       "add");
 
     } else
-    if (msg instanceof ModifyMsg)
-    {
-      ModifyMsg modMsg = (ModifyMsg)msg;
-      InternalClientConnection conn =
-        InternalClientConnection.getRootConnection();
-      try
+      if (msg instanceof ModifyMsg)
       {
-        // Map the modMsg modifications to an LDIF string
-        // for the 'changes' attribute of the CL entry
-        ModifyOperation modifyOperation =
-          (ModifyOperation)modMsg.createOperation(conn);
-        String LDIFchanges = modToLDIF(modifyOperation.getModifications());
+        ModifyMsg modMsg = (ModifyMsg)msg;
+        InternalClientConnection conn =
+          InternalClientConnection.getRootConnection();
+        try
+        {
+          // Map the modMsg modifications to an LDIF string
+          // for the 'changes' attribute of the CL entry
+          ModifyOperation modifyOperation =
+            (ModifyOperation)modMsg.createOperation(conn);
+          String LDIFchanges = modToLDIF(modifyOperation.getModifications());
 
-        // TODO:ECL G Good changelog draft compat. Hist entry attributes
-        // ArrayList<RawAttribute> attributes = modMsg.getEntryAttributes();
+          // TODO:ECL Hist entry attributes
+          // ArrayList<RawAttribute> attributes = modMsg.getEntryAttributes();
+          clEntry = createChangelogEntry(
+              eclmsg.getServiceId(),
+              eclmsg.getCookie().toString(),
+              DN.decode(modMsg.getDn()),
+              modMsg.getChangeNumber(),
+              LDIFchanges,
+              modMsg.getUniqueId(),
+              null, // real time current entry
+              null, // real time attrs names
+              null, // hist entry attributes
+              eclmsg.getDraftChangeNumber(),
+          "modify");
+
+        }
+        catch(Exception e)
+        {
+          // Exceptions raised by createOperation for example
+          throw new DirectoryException(ResultCode.OTHER,
+              Message.raw(Category.SYNC, Severity.NOTICE,
+                  " Server fails to create entry: "),e);
+        }
+      }
+      else if (msg instanceof ModifyDNMsg)
+      {
+        ModifyDNMsg modDNMsg = (ModifyDNMsg)msg;
+
         clEntry = createChangelogEntry(
             eclmsg.getServiceId(),
             eclmsg.getCookie().toString(),
-            DN.decode(modMsg.getDn()),
-            modMsg.getChangeNumber(),
-            LDIFchanges,
-            modMsg.getUniqueId(),
+            DN.decode(modDNMsg.getDn()),
+            modDNMsg.getChangeNumber(),
+            null,
+            modDNMsg.getUniqueId(),
             null, // real time current entry
             null, // real time attrs names
             null, // hist entry attributes
-            -1,    // TODO:ECL G Good changelog draft compat. modMsg.getSeqnum()
-        "modify");
+            eclmsg.getDraftChangeNumber(),
+        "modrdn");
+
+        Attribute a = Attributes.create("newrdn", modDNMsg.getNewRDN());
+        clEntry.addAttribute(a, null);
+
+        Attribute b = Attributes.create("newsuperior",
+            modDNMsg.getNewSuperior());
+        clEntry.addAttribute(b, null);
+
+        Attribute c = Attributes.create("deleteoldrdn",
+            String.valueOf(modDNMsg.deleteOldRdn()));
+        clEntry.addAttribute(c, null);
 
       }
-      catch(Exception e)
+      else if (msg instanceof DeleteMsg)
       {
-        // FIXME:ECL Handle error when createOperation raise DataFormatExceptin
+        DeleteMsg delMsg = (DeleteMsg)msg;
+        /* TODO:ECL Entry attributes for DEL op
+        ArrayList<RawAttribute> rattributes = new ArrayList<RawAttribute>();
+        ArrayList<RawAttribute> rattributes = delMsg.getEntryAttributes();
+        // Map the entry attributes of the DelMsg to an LDIF string
+        // for the 'deletedentryattributes' attribute of the CL entry
+        String delAttrs = delMsgToLDIFString(rattributes);
+        */
+        clEntry = createChangelogEntry(
+            eclmsg.getServiceId(),
+            eclmsg.getCookie().toString(),
+            DN.decode(delMsg.getDn()),
+            delMsg.getChangeNumber(),
+            null,
+            delMsg.getUniqueId(),
+            null,
+            null,
+            null, //rattributes,
+            eclmsg.getDraftChangeNumber(),
+        "delete");
       }
-    }
-    else if (msg instanceof ModifyDNMsg)
-    {
-      ModifyDNMsg modDNMsg = (ModifyDNMsg)msg;
-
-      clEntry = createChangelogEntry(
-          eclmsg.getServiceId(),
-          eclmsg.getCookie().toString(),
-          DN.decode(modDNMsg.getDn()),
-          modDNMsg.getChangeNumber(),
-          null,
-          modDNMsg.getUniqueId(),
-          null, // real time current entry
-          null, // real time attrs names
-          null, // hist entry attributes
-          -1,    // TODO:ECL G Good changelog draft compat. modDNMsg.getSeqnum()
-          "modrdn");
-
-      Attribute a = Attributes.create("newrdn", modDNMsg.getNewRDN());
-      clEntry.addAttribute(a, null);
-
-      Attribute b = Attributes.create("newsuperior", modDNMsg.getNewSuperior());
-      clEntry.addAttribute(b, null);
-
-      Attribute c = Attributes.create("deleteoldrdn",
-          String.valueOf(modDNMsg.deleteOldRdn()));
-      clEntry.addAttribute(c, null);
-
-    }
-    else if (msg instanceof DeleteMsg)
-    {
-      DeleteMsg delMsg = (DeleteMsg)msg;
-      ArrayList<RawAttribute> rattributes = new ArrayList<RawAttribute>();
-      /* TODO:ECL Entry attributes for DEL op
-      ArrayList<RawAttribute> rattributes = delMsg.getEntryAttributes();
-      // Map the entry attributes of the DelMsg to an LDIF string
-      // for the 'deletedentryattributes' attribute of the CL entry
-      String delAttrs = delMsgToLDIFString(rattributes);
-      */
-      clEntry = createChangelogEntry(
-          eclmsg.getServiceId(),
-          eclmsg.getCookie().toString(),
-          DN.decode(delMsg.getDn()),
-          delMsg.getChangeNumber(),
-          null,
-          delMsg.getUniqueId(),
-          null,
-          null,
-          null, //rattributes,
-          -1, // TODO:ECL G Good changelog draft compat. delMsg.getSeqnum()
-         "delete");
-    }
     return clEntry;
   }
 
@@ -863,20 +841,6 @@
     HashMap<AttributeType,List<Attribute>> operationalAttrs =
       new LinkedHashMap<AttributeType,List<Attribute>>();
 
-    // Add to the root entry the replication state of each domain
-    // TODO:ECL Put in ECL root entry, the ServerState for each domain
-    AttributeType descType = DirectoryServer.getAttributeType("description");
-    List<ReplicationDomain> supportedReplicationDomains
-     = new ArrayList<ReplicationDomain>();
-
-    for (ReplicationDomain domain : supportedReplicationDomains)
-    {
-      //    Crappy stuff to return the server state to the client
-      LinkedList<Attribute> attrList = new LinkedList<Attribute>();
-      attrList.add(Attributes.create("description",
-          domain.getServiceID() + "/" + domain.getServerState().toString()));
-      userAttrs.put(descType, attrList);
-    }
     Entry e = new Entry(this.rootBaseDN, oclasses, userAttrs,
         operationalAttrs);
     return e;
@@ -916,10 +880,19 @@
       String changetype)
   throws DirectoryException
   {
-    String dnString = "cn="+ changeNumber +"," +
-      serviceID + "," +
+    String dnString = "";
+    if (draftChangenumber == 0)
+    {
+      // Draft uncompat mode
+      dnString = "cn="+ changeNumber +"," + serviceID + "," +
+        ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT;
+    }
+    else
+    {
+      // Draft compat mode
+      dnString = "cn="+ draftChangenumber + "," +
       ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT;
-
+    }
     HashMap<ObjectClass,String> oClasses =
       new LinkedHashMap<ObjectClass,String>(3);
     oClasses.putAll(eclObjectClasses);
@@ -962,7 +935,7 @@
     attrList = new ArrayList<Attribute>(1);
     attrList.add(a);
     uAttrs.put(a.getAttributeType(), attrList);
-    */
+     */
 
     //
     a = Attributes.create("changetype", changetype);
@@ -1017,18 +990,21 @@
       uAttrs.put(a.getAttributeType(), attrList);
     }
 
-    a = Attributes.create("targetentryuuid", targetUUID);
-    attrList = new ArrayList<Attribute>(1);
-    attrList.add(a);
-    operationalAttrs.put(a.getAttributeType(), attrList);
-    if (draftChangenumber>0)
+    if (targetUUID != null)
     {
-      // compat mode
-      a = Attributes.create("targetuniqueid",
-          ECLSearchOperation.openDsToSunDseeNsUniqueId(targetUUID));
+      a = Attributes.create("targetentryuuid", targetUUID);
       attrList = new ArrayList<Attribute>(1);
       attrList.add(a);
       operationalAttrs.put(a.getAttributeType(), attrList);
+      if (draftChangenumber>0)
+      {
+        // compat mode
+        a = Attributes.create("targetuniqueid",
+            ECLSearchOperation.openDsToSunDseeNsUniqueId(targetUUID));
+        attrList = new ArrayList<Attribute>(1);
+        attrList.add(a);
+        operationalAttrs.put(a.getAttributeType(), attrList);
+      }
     }
 
     a = Attributes.create("changelogcookie", cookie);
@@ -1061,7 +1037,7 @@
         uAttrs.put(a.getAttributeType(), attrList);
       }
     }
-    */
+     */
 
     /*
     if (targetAttrNames != null)
@@ -1082,7 +1058,7 @@
         }
       }
     }
-    */
+     */
     /* TODO: Implement entry attributes historical values
     if (histEntryAttributes != null)
     {
@@ -1105,7 +1081,7 @@
         }
       }
     }
-    */
+     */
 
     // at the end build the CL entry to be returned
     Entry cle = new Entry(
@@ -1315,7 +1291,7 @@
    */
   public CancelResult cancel(CancelRequest cancelRequest)
   {
-    if (eclSession!=null)
+    if (eclSession != null)
     {
       try
       {
@@ -1327,20 +1303,16 @@
   }
 
   /**
-  * The unique identifier used in DSEE is named nsUniqueId and its format is
-  * HHHHHHHH-HHHHHHHH-HHHHHHHH-HHHHHHHH where H is a hex digit.
-  * An nsUniqueId value is for example 3970de28-08b311d9-8095b9bf-c4d9231c
-  * The unique identifier used in OpenDS is named entryUUID.
-  * Its value is for example entryUUID: 50dd9673-71e1-4478-b13c-dba387c4d7e1
-  * @param entryUid the OpenDS entry UID
-  * @return the Dsee format for the entry UID
-  */
+   * The unique identifier used in DSEE is named nsUniqueId and its format is
+   * HHHHHHHH-HHHHHHHH-HHHHHHHH-HHHHHHHH where H is a hex digit.
+   * An nsUniqueId value is for example 3970de28-08b311d9-8095b9bf-c4d9231c
+   * The unique identifier used in OpenDS is named entryUUID.
+   * Its value is for example entryUUID: 50dd9673-71e1-4478-b13c-dba387c4d7e1
+   * @param entryUid the OpenDS entry UID
+   * @return the Dsee format for the entry UID
+   */
   private static String openDsToSunDseeNsUniqueId(String entryUid)
   {
-
-    if (entryUid == null)
-      return null;
-
     //  the conversion from one unique identifier to an other is
     //  a question of formating : the last "-" is placed
     StringBuffer buffer = new StringBuffer(entryUid);
@@ -1355,5 +1327,106 @@
 
     return buffer.toString();
   }
-}
 
+  /**
+   * Traverse the provided search filter, looking for some conditions
+   * on attributes that can be optimized in the ECL.
+   * When found, populate the provided StartECLSessionMsg.
+   * @param startCLmsg the startCLMsg to be populated.
+   * @param sf the provided search filter.
+   * @throws DirectoryException when an exception occurs.
+   */
+  public static void evaluateFilter(StartECLSessionMsg startCLmsg,
+      SearchFilter sf)
+  throws DirectoryException
+  {
+    StartECLSessionMsg msg = evaluateFilter2(sf);
+    startCLmsg.setFirstDraftChangeNumber(msg.getFirstDraftChangeNumber());
+    startCLmsg.setLastDraftChangeNumber(msg.getLastDraftChangeNumber());
+    startCLmsg.setChangeNumber(msg.getChangeNumber());
+  }
+
+  private static StartECLSessionMsg evaluateFilter2(SearchFilter sf)
+  throws DirectoryException
+  {
+    StartECLSessionMsg startCLmsg = new StartECLSessionMsg();
+    startCLmsg.setFirstDraftChangeNumber(-1);
+    startCLmsg.setLastDraftChangeNumber(-1);
+    startCLmsg.setChangeNumber(new ChangeNumber(0,0,(short)0));
+
+    // Here are the 3 elementary cases we know how to optimize
+    if ((sf != null)
+        && (sf.getFilterType() == FilterType.GREATER_OR_EQUAL)
+        && (sf.getAttributeType() != null)
+        && (sf.getAttributeType().getPrimaryName().
+            equalsIgnoreCase("changeNumber")))
+    {
+      int sn = Integer.decode(
+          sf.getAssertionValue().getNormalizedValue().toString());
+      startCLmsg.setFirstDraftChangeNumber(sn);
+      return startCLmsg;
+    }
+    else if ((sf != null)
+        && (sf.getFilterType() == FilterType.LESS_OR_EQUAL)
+        && (sf.getAttributeType() != null)
+        && (sf.getAttributeType().getPrimaryName().
+            equalsIgnoreCase("changeNumber")))
+    {
+      int sn = Integer.decode(
+          sf.getAssertionValue().getNormalizedValue().toString());
+      startCLmsg.setLastDraftChangeNumber(sn);
+      return startCLmsg;
+    }
+    else if ((sf != null)
+        && (sf.getFilterType() == FilterType.EQUALITY)
+        && (sf.getAttributeType() != null)
+        && (sf.getAttributeType().getPrimaryName().
+            equalsIgnoreCase("replicationcsn")))
+    {
+      // == exact changenumber
+      ChangeNumber cn = new ChangeNumber(sf.getAssertionValue().toString());
+      startCLmsg.setChangeNumber(cn);
+      return startCLmsg;
+    }
+    else if ((sf != null)
+        && (sf.getFilterType() == FilterType.EQUALITY)
+        && (sf.getAttributeType() != null)
+        && (sf.getAttributeType().getPrimaryName().
+            equalsIgnoreCase("changenumber")))
+    {
+      int sn = Integer.decode(
+          sf.getAssertionValue().getNormalizedValue().toString());
+      startCLmsg.setFirstDraftChangeNumber(sn);
+      startCLmsg.setLastDraftChangeNumber(sn);
+      return startCLmsg;
+    }
+    else if ((sf != null)
+        && (sf.getFilterType() == FilterType.AND))
+    {
+      // Here is the only binary operation we know how to optimize
+      Collection<SearchFilter> comps = sf.getFilterComponents();
+      SearchFilter sfs[] = comps.toArray(new SearchFilter[0]);
+      StartECLSessionMsg m1 = evaluateFilter2(sfs[0]);
+      StartECLSessionMsg m2 = evaluateFilter2(sfs[1]);
+
+      int l1 = m1.getLastDraftChangeNumber();
+      int l2 = m2.getLastDraftChangeNumber();
+      if (l1 == -1)
+        startCLmsg.setLastDraftChangeNumber(l2);
+      else
+        if (l2 == -1)
+          startCLmsg.setLastDraftChangeNumber(l1);
+        else
+          startCLmsg.setLastDraftChangeNumber(Math.min(l1,l2));
+
+      int f1 = m1.getFirstDraftChangeNumber();
+      int f2 = m2.getFirstDraftChangeNumber();
+      startCLmsg.setFirstDraftChangeNumber(Math.max(f1,f2));
+      return startCLmsg;
+    }
+    else
+    {
+      return startCLmsg;
+    }
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
index 8581738..b0d20bc 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
@@ -171,12 +171,12 @@
 
   /**
    * Registers the provided persistent search operation with this
-   * local backend workflow element so that it will be notified of any
+   * 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.
+   *          workflow element.
    */
   void registerPersistentSearch(PersistentSearch persistentSearch)
   {
@@ -198,10 +198,10 @@
 
   /**
    * Gets the list of persistent searches currently active against
-   * this local backend workflow element.
+   * this workflow element.
    *
    * @return The list of persistent searches currently active against
-   *         this local backend workflow element.
+   *         this workflow element.
    */
   public List<PersistentSearch> getPersistentSearches()
   {
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ExternalChangeLogTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ExternalChangeLogTest.java
index c0a82ef..785d5f6 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ExternalChangeLogTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ExternalChangeLogTest.java
@@ -82,6 +82,7 @@
 import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
 import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
 import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.ChangeNumberGenerator;
 import org.opends.server.replication.common.MultiDomainServerState;
 import org.opends.server.replication.common.ServerState;
 import org.opends.server.replication.plugin.DomainFakeCfg;
@@ -95,7 +96,9 @@
 import org.opends.server.replication.protocol.ModifyDnContext;
 import org.opends.server.replication.protocol.ModifyMsg;
 import org.opends.server.replication.protocol.ReplicationMsg;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
 import org.opends.server.replication.protocol.UpdateMsg;
+import org.opends.server.replication.server.DraftCNDbHandler;
 import org.opends.server.replication.server.ExternalChangeLogSessionImpl;
 import org.opends.server.replication.server.ReplServerFakeConfiguration;
 import org.opends.server.replication.server.ReplicationServer;
@@ -118,15 +121,18 @@
 import org.opends.server.types.ModificationType;
 import org.opends.server.types.RDN;
 import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchScope;
 import org.opends.server.util.LDIFWriter;
 import org.opends.server.util.TimeThread;
+import org.opends.server.workflowelement.externalchangelog.ECLSearchOperation;
 import org.opends.server.workflowelement.localbackend.LocalBackendModifyDNOperation;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+
 /**
  * Tests for the replicationServer code.
  */
@@ -142,12 +148,16 @@
   // The port of the replicationServer.
   private int replicationServerPort;
 
-  public static final String TEST_ROOT_DN_STRING2 = "o=test2";
-  public static final String TEST_BACKEND_ID2 = "test2";
+  private static final String TEST_ROOT_DN_STRING2 = "o=test2";
+  private static final String TEST_BACKEND_ID2 = "test2";
 
   // The LDAPStatistics object associated with the LDAP connection handler.
-  protected LDAPStatistics ldapStatistics;
-  
+  private LDAPStatistics ldapStatistics;
+
+  private ChangeNumber gblCN;
+
+  List<Control> NO_CONTROL = null;
+
   /**
    * Set up the environment for performing the tests in this Class.
    * Replication
@@ -181,7 +191,7 @@
 
     replicationServer = new ReplicationServer(conf1);;
     debugInfo("configure", "ReplicationServer created"+replicationServer);
-    
+
   }
 
   /**
@@ -190,25 +200,47 @@
   @Test(enabled=true)
   public void ECLReplicationServerTest()
   {
+    ECLOnPrivateBackend();replicationServer.clearDb();
     ECLRemoteEmpty();replicationServer.clearDb();
-    ECLDirectEmpty();replicationServer.clearDb();
-    ECLDirectAllOps();replicationServer.clearDb();
+    ECLEmpty();replicationServer.clearDb();
+    ECLAllOps();replicationServer.clearDb();
     ECLRemoteNonEmpty();replicationServer.clearDb();
-    ECLDirectMsg();replicationServer.clearDb();
-    ECLDirectPsearch(true);replicationServer.clearDb();
-    ECLDirectPsearch(false);replicationServer.clearDb();
-    ECLPrivateDirectMsg();replicationServer.clearDb();
+    ECLTwoDomains();replicationServer.clearDb();
+    ECLPsearch(true, false);replicationServer.clearDb();
+    ECLPsearch(false, false);replicationServer.clearDb();
+    ECLSimulPsearches();replicationServer.clearDb();
+
     // TODO:ECL Test SEARCH abandon and check everything shutdown and cleaned
     // TODO:ECL Test PSEARCH abandon and check everything shutdown and cleaned
     // TODO:ECL Test invalid DN in cookie returns UNWILLING + message
     // TODO:ECL Test notif control returned contains the cookie
     // TODO:ECL Test the attributes list and values returned in ECL entries
     // TODO:ECL Test search -s base, -s one
+    ChangeTimeHeartbeatTest();replicationServer.clearDb();
+    ECLFilterTest();
+
+    ECLCompatEmpty();
+    ECLCompatBadSeqnum();
+    ECLCompatWriteReadAllOps(1);
+    ECLCompatWriteReadAllOps(5);
+    ECLCompatReadFrom(6);
+    ECLCompatReadFromTo(5,7);
+    ECLCompatTestLimits(1,8);
+    ECLCompatPurge();
+    ECLCompatTestLimits(0,0);
+    ECLPsearch(true, true);replicationServer.clearDb();
+    ECLPsearch(false, true);
+    ECLFilterOnReplicationCsn();replicationServer.clearDb();
+    ECLSimulPsearches();replicationServer.clearDb();
+    
   }
 
   //=======================================================
-  // From 3 remote ECL session,
-  // Test DoneMsg is received from 1 suffix on each session.
+  // Objectives
+  //   - Test that everything id ok with no changes
+  // Procedure
+  //   - Does a SEARCH from 3 different remote ECL session,
+  //   - Verify  DoneMsg is received on each session.
   private void ECLRemoteEmpty()
   {
     String tn = "ECLRemoteEmpty";
@@ -244,9 +276,11 @@
       }
       while(!(msg instanceof DoneMsg));
       assertTrue(msgc==1,
-          "Ending " + tn + " with incorrect message type :" +
+          "Ending " + tn + " with incorrect message number :" +
           msg.getClass().getCanonicalName());
-      assertTrue(msg instanceof DoneMsg);        
+      assertTrue(msg instanceof DoneMsg,
+      "Ending " + tn + " with incorrect message type :" +
+      msg.getClass().getCanonicalName());
 
       // Test broker2 receives only Done
       msgc=0;
@@ -257,8 +291,11 @@
       }
       while(!(msg instanceof DoneMsg));
       assertTrue(msgc==1,
-          "Ending " + tn + " with incorrect message type :" +
+          "Ending " + tn + " with incorrect message number :" +
           msg.getClass().getCanonicalName());
+      assertTrue(msg instanceof DoneMsg,
+      "Ending " + tn + " with incorrect message type :" +
+      msg.getClass().getCanonicalName());
 
       // Test broker3 receives only Done
       msgc=0;
@@ -269,26 +306,31 @@
       }
       while(!(msg instanceof DoneMsg));
       assertTrue(msgc==1,
-          "Ending " + tn + " with incorrect message type :" +
+          "Ending " + tn + " with incorrect message number :" +
           msg.getClass().getCanonicalName());
+      assertTrue(msg instanceof DoneMsg,
+      "Ending " + tn + " with incorrect message type :" +
+      msg.getClass().getCanonicalName());
 
       server1.stop();
       server2.stop();
       server3.stop();
       sleep(500);
-      debugInfo(tn, "Ending test successfully\n\n");      
+      debugInfo(tn, "Ending test successfully\n\n");    
     }
     catch(Exception e)
     {
-      debugInfo(tn, "Ending test with exception:"
+      fail("Ending test " + tn +  " with exception:"
           +  stackTraceToSingleLineString(e));      
-      assertTrue(e == null);
     }
   }
-  
+
   //=======================================================
-  // From 1 remote ECL session,
-  // test simple update to be received from 2 suffixes
+  // Objectives
+  //   - Test that everything id ok with changes on 2 suffixes
+  // Procedure
+  //   - From 1 remote ECL session,
+  //   - Test simple update to be received from 2 suffixes
   private void ECLRemoteNonEmpty()
   {
     String tn = "ECLRemoteNonEmpty";
@@ -331,7 +373,7 @@
 
       // wait for the server to take these changes into account
       sleep(500);
-      
+
       // open ECL broker
       serverECL = openReplicationSession(
           DN.decode("cn=changelog"), (short)10,
@@ -343,11 +385,11 @@
       msg = serverECL.receive();
       ECLUpdateMsg eclu = (ECLUpdateMsg)msg;
       UpdateMsg u = eclu.getUpdateMsg();
-      debugInfo(tn, "RESULT:" + u.getChangeNumber());
+      debugInfo(tn, "RESULT:" + u.getChangeNumber() + " " + eclu.getCookie());
       assertTrue(u.getChangeNumber().equals(cn1), "RESULT:" + u.getChangeNumber());
       assertTrue(eclu.getCookie().equalsTo(new MultiDomainServerState(
-          "o=test:"+delMsg1.getChangeNumber()+";")));
-      
+          "o=test:"+delMsg1.getChangeNumber()+";o=test2:;")));
+
       // receive change 2 from suffix 2
       msg = serverECL.receive();
       eclu = (ECLUpdateMsg)msg;
@@ -355,8 +397,8 @@
       debugInfo(tn, "RESULT:" + u.getChangeNumber());
       assertTrue(u.getChangeNumber().equals(cn2), "RESULT:" + u.getChangeNumber());
       assertTrue(eclu.getCookie().equalsTo(new MultiDomainServerState(
-      "o=test2:"+delMsg2.getChangeNumber()+";"+
-      "o=test:"+delMsg1.getChangeNumber()+";")));
+          "o=test2:"+delMsg2.getChangeNumber()+";"+
+          "o=test:"+delMsg1.getChangeNumber()+";")));
 
       // receive Done
       msg = serverECL.receive();
@@ -372,35 +414,23 @@
     }
     catch(Exception e)
     {
-      debugInfo(tn, "Ending test with exception:"
+      fail("Ending test " + tn + " with exception:"
           +  stackTraceToSingleLineString(e));      
-      assertTrue(e == null);
     }
   }
-  
+
   /**
    * From embedded ECL (no remote session)
    * With empty RS, simple search should return only root entry.
    */
-  private void ECLDirectEmpty()
+  private void ECLEmpty()
   {
-    String tn = "ECLDirectEmpty";
+    String tn = "ECLEmpty";
     debugInfo(tn, "Starting test\n\n");
 
     try
     {
       // search on 'cn=changelog'
-      InternalSearchOperation op = connection.processSearch(
-        ByteString.valueOf("cn=changelog"),
-        SearchScope.WHOLE_SUBTREE,
-        LDAPFilter.decode("(objectclass=*)"));
-
-      // Error because no cookie
-      assertEquals(
-          op.getResultCode(), ResultCode.OPERATIONS_ERROR,
-          op.getErrorMessage().toString());
-
-      // search on 'cn=changelog'
       InternalSearchOperation op2 = connection.processSearch(
           ByteString.valueOf("cn=changelog"),
           SearchScope.WHOLE_SUBTREE,
@@ -409,7 +439,7 @@
           false,
           LDAPFilter.decode("(objectclass=*)"),
           new LinkedHashSet<String>(0),
-          getControls(""),
+          createControls(""),
           null);
 
       // success
@@ -423,13 +453,17 @@
     }
     catch(LDAPException e)
     {
-      debugInfo(tn, "Ending test with exception e="
+      fail("Ending test " + tn + " with exception e="
           +  stackTraceToSingleLineString(e));      
-      assertTrue(e == null);
     }
   }
 
-  private ArrayList<Control> getControls(String cookie)
+  /**
+   * Build a list of controls including the cookie provided.
+   * @param cookie The provided cookie.
+   * @return The built list of controls.
+   */
+  private ArrayList<Control> createControls(String cookie)
   {
     ExternalChangelogRequestControl control =
       new ExternalChangelogRequestControl(true, 
@@ -439,7 +473,9 @@
     return controls;
   }
 
-  // Utility - creates an LDIFWriter to dump result entries
+  /**
+   * Utility - creates an LDIFWriter to dump result entries.
+   */
   private static LDIFWriter getLDIFWriter()
   {
     LDIFWriter ldifWriter = null;
@@ -470,46 +506,41 @@
     assertNotNull(getEntry(entry.getDN(), 1000, true));
   }
 
-  //=======================================================
-  // From embebbded ECL
-  // Search ECL with 1 domain, public then private backend
-  private void ECLPrivateDirectMsg()
+  private void ECLOnPrivateBackend()
   {
-    ReplicationServer replServer = null;
-    String tn = "ECLPrivateDirectMsg";
+    String tn = "ECLOnPrivateBackend";
     debugInfo(tn, "Starting test");
-
     try
     {
-      // Initialize a second test backend
+      // Initialize a second test backend o=test2, in addtion to o=test
+      // Configure replication on this backend
+      // Add the root entry in the backend
       Backend backend2 = initializeTestBackend2(false);
-
-      DN baseDn = DN.decode(TEST_ROOT_DN_STRING2);
-
-      // configure and start replication of TEST_ROOT_DN_STRING on the server
+      DN baseDn2 = DN.decode(TEST_ROOT_DN_STRING2);
       SortedSet<String> replServers = new TreeSet<String>();
       replServers.add("localhost:"+replicationServerPort);
       DomainFakeCfg domainConf =
-        new DomainFakeCfg(baseDn, (short) 1602, replServers);
-      LDAPReplicationDomain domain = MultimasterReplication.createNewDomain(domainConf);
+        new DomainFakeCfg(baseDn2, (short) 1602, replServers);
+      LDAPReplicationDomain domain2 = MultimasterReplication.createNewDomain(domainConf);
       SynchronizationProvider replicationPlugin = new MultimasterReplication();
       replicationPlugin.completeSynchronizationProvider();
       sleep(1000);
-
-      Entry e = createEntry(baseDn);
+      Entry e = createEntry(baseDn2);
       addEntry(e);
-      sleep(1000);
 
+      // Search on ECL from start on all suffixes
+      String cookie = "";
       ExternalChangelogRequestControl control =
         new ExternalChangelogRequestControl(true, 
-            new MultiDomainServerState(""));
+            new MultiDomainServerState(cookie));
       ArrayList<Control> controls = new ArrayList<Control>(0);
       controls.add(control);
-
-      // search on 'cn=changelog'
       LinkedHashSet<String> attributes = new LinkedHashSet<String>();
       attributes.add("+");
       attributes.add("*");
+
+      debugInfo(tn, "Search with cookie=" + cookie);
+      sleep(2000);
       InternalSearchOperation searchOp = connection.processSearch(
           ByteString.valueOf("cn=changelog"),
           SearchScope.WHOLE_SUBTREE,
@@ -521,23 +552,27 @@
           attributes,
           controls,
           null);
+
+      // Expect SUCCESS and root entry returned
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
           searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
-
       LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
       assertTrue(entries != null);
       if (entries != null)
-      {
         for (SearchResultEntry resultEntry : entries)
         {
-          debugInfo(tn, "Result private entry=" + resultEntry.toLDIFString());
+          // Expect 
+          debugInfo(tn, "Entry returned=" + resultEntry.toLDIFString());
         }
-      }
       assertEquals(entries.size(),1, "Entries number returned by search");
-      
-      // Same with private backend
-      domain.getBackend().setPrivateBackend(true);
 
+      //
+      // Set the backend private and do again a search on ECL that should
+      // now not return the entry
+      //
+      domain2.getBackend().setPrivateBackend(true);
+
+      debugInfo(tn, "Search with cookie=" + cookie);
       searchOp = connection.processSearch(
           ByteString.valueOf("cn=changelog"),
           SearchScope.WHOLE_SUBTREE,
@@ -549,41 +584,37 @@
           attributes,
           controls,
           null);
+      
+      // Expect success but no entry returned
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
           searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
-
       entries = searchOp.getSearchEntries();
       assertTrue(entries != null);
       assertTrue(entries.size()==0);
-      
-      if (replServer != null)
-        replServer.remove();
 
-      if (domain != null)
-        MultimasterReplication.deleteDomain(baseDn);
-
+      // Cleaning
+      if (domain2 != null)
+        MultimasterReplication.deleteDomain(baseDn2);
       if (replicationPlugin != null)
         DirectoryServer.deregisterSynchronizationProvider(replicationPlugin);
-
       removeTestBackend2(backend2);
     }
     catch(Exception e)
     {
-      debugInfo(tn, "Ending test ECLDirectMsg with exception:\n"
+      fail("Ending test " + tn + " with exception:"
           +  stackTraceToSingleLineString(e));      
-      assertTrue(e == null);
     }
     debugInfo(tn, "Ending test successfully");
   }
-  
+
   //=======================================================
   // From embebbded ECL
   // Search ECL with 4 messages on 2 suffixes from 2 brokers
-  private void ECLDirectMsg()
+  private void ECLTwoDomains()
   {
-    String tn = "ECLDirectMsg";
+    String tn = "ECLTwoDomains";
     debugInfo(tn, "Starting test");
- 
+
     try
     {
       // Initialize a second test backend
@@ -630,7 +661,7 @@
         new DeleteMsg("uid="+tn+"4," + TEST_ROOT_DN_STRING, cn, tn+"uuid4");
       s1test.publish(delMsg);
       debugInfo(tn, " publishes " + delMsg.getChangeNumber());
-      sleep(500);
+      sleep(1500);
 
       // Changes are :
       //               s1          s2
@@ -638,29 +669,28 @@
       // o=test2                 msg2/msg2
       String cookie= "";
 
-      debugInfo(tn, "STEP 1 - from empty cookie("+cookie+")");
-
       // search on 'cn=changelog'
       LinkedHashSet<String> attributes = new LinkedHashSet<String>();
       attributes.add("+");
       attributes.add("*");
+
+      debugInfo(tn, "Search with cookie=" + cookie + "\"");
       InternalSearchOperation searchOp = 
         connection.processSearch(
             ByteString.valueOf("cn=changelog"),
-          SearchScope.WHOLE_SUBTREE,
-          DereferencePolicy.NEVER_DEREF_ALIASES, 
-          0, // Size limit
-          0, // Time limit
-          false, // Types only
-          LDAPFilter.decode("(targetDN=*direct*)"),
-          attributes,
-          getControls(cookie),
-          null);
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 
+            0, // Size limit
+            0, // Time limit
+            false, // Types only
+            LDAPFilter.decode("(targetDN=*"+tn+"*)"),
+            attributes,
+            createControls(cookie),
+            null);
 
-      // We expect SUCCESS and the 4 changes
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
           searchOp.getErrorMessage().toString());
-      
+      cookie="";
       LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
       if (entries != null)
       {
@@ -669,35 +699,30 @@
         {
           debugInfo(tn, " RESULT entry returned:" + entry.toSingleLineString());
           ldifWriter.writeEntry(entry);
-          try
+          if (i++==2)
           {
-            if (i++==2)
-              cookie =
-                entry.getAttribute("changelogcookie").get(0).iterator().next().toString();
+            // Store the cookie returned with the 3rd ECL entry returned to use
+            // it in the test below.
+            cookie =
+              entry.getAttribute("changelogcookie").get(0).iterator().next().toString();
           }
-          catch(NullPointerException e)
-          {}
         }
       }
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS);
-      
-      // We expect the 4 changes
       assertEquals(searchOp.getSearchEntries().size(), 4);
 
-      // Now start from last cookie and expect to get the last change
-      // search on 'cn=changelog'
+      // Now start from last cookie and expect to get ONLY the 4th change
       attributes = new LinkedHashSet<String>();
       attributes.add("+");
       attributes.add("*");
 
-      debugInfo(tn, "STEP 2 - from cookie" + cookie);
-
       ExternalChangelogRequestControl control =
         new ExternalChangelogRequestControl(true, 
             new MultiDomainServerState(cookie));
       ArrayList<Control> controls = new ArrayList<Control>(0);
       controls.add(control);
 
+      debugInfo(tn, "Search with cookie=" + cookie);
       searchOp = connection.processSearch(
           ByteString.valueOf("cn=changelog"),
           SearchScope.WHOLE_SUBTREE,
@@ -705,18 +730,16 @@
           0, // Size limit
           0, // Time limit
           false, // Types only
-          LDAPFilter.decode("(targetDN=*direct*)"),
+          LDAPFilter.decode("(targetDN=*"+tn+"*)"),
           attributes,
           controls,
           null);
-      
-      
+
       // We expect SUCCESS and the 4th change
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
           searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
-      
-      cookie= "";
       entries = searchOp.getSearchEntries();
+      cookie="";
       if (entries != null)
       {
         for (SearchResultEntry entry : entries)
@@ -725,21 +748,18 @@
           ldifWriter.writeEntry(entry);
           try
           {
-          cookie =
-            entry.getAttribute("changelogcookie").get(0).iterator().next().toString();
+            // Store the cookie returned with the 4rd ECL entry returned to use
+            // it in the test below.
+            cookie =
+              entry.getAttribute("changelogcookie").get(0).iterator().next().toString();
           }
           catch(NullPointerException e)
           {}
         }
       }
-
-      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS);
-      
-      // we expect msg4
       assertEquals(searchOp.getSearchEntries().size(), 1);
-      
-      debugInfo(tn, "STEP 3 - from cookie" + cookie);
 
+      // Now publishes a new change and search from the previous cookie
       ChangeNumber cn5 = new ChangeNumber(time++, ts++, s1test.getServerId());
       delMsg =
         new DeleteMsg("uid="+tn+"5," + TEST_ROOT_DN_STRING, cn5, tn+"uuid5");
@@ -757,6 +777,7 @@
       controls = new ArrayList<Control>(0);
       controls.add(control);
 
+      debugInfo(tn, "Search with cookie=" + cookie + "\"");
       searchOp = connection.processSearch(
           ByteString.valueOf("cn=changelog"),
           SearchScope.WHOLE_SUBTREE,
@@ -764,15 +785,13 @@
           0, // Size limit
           0, // Time limit
           false, // Types only
-          LDAPFilter.decode("(targetDN=*direct*)"),
+          LDAPFilter.decode("(targetDN=*"+tn+"*)"),
           attributes,
           controls,
           null);
 
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
           searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
-      
-      cookie= "";
       entries = searchOp.getSearchEntries();
       if (entries != null)
       {
@@ -782,20 +801,17 @@
           ldifWriter.writeEntry(resultEntry);
           try
           {
-          cookie =
-            resultEntry.getAttribute("changelogcookie").get(0).iterator().next().toString();
+            cookie =
+              resultEntry.getAttribute("changelogcookie").get(0).iterator().next().toString();
           }
           catch(NullPointerException e)
           {}
         }
       }
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS);
-
-      // we expect 1 entries : msg5
       assertEquals(searchOp.getSearchEntries().size(), 1);
 
       cookie="";
-      debugInfo(tn, "STEP 4 - [filter:o=test cookie:" + cookie + "]");
 
       control =
         new ExternalChangelogRequestControl(true, 
@@ -803,6 +819,8 @@
       controls = new ArrayList<Control>(0);
       controls.add(control);
 
+      debugInfo(tn, "Search with cookie=" + cookie + "\" and filter on domain=" + 
+          "(targetDN=*direct*,o=test)");
       searchOp = connection.processSearch(
           ByteString.valueOf("cn=changelog"),
           SearchScope.WHOLE_SUBTREE,
@@ -810,15 +828,15 @@
           0, // Size limit
           0, // Time limit
           false, // Types only
-          LDAPFilter.decode("(targetDN=*direct*,o=test)"),
+          LDAPFilter.decode("(targetDN=*"+tn+"*,o=test)"),
           attributes,
           controls,
           null);
-      
-      
+
+
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
           searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
-      
+
       entries = searchOp.getSearchEntries();
       if (entries != null)
       {
@@ -828,18 +846,20 @@
           ldifWriter.writeEntry(resultEntry);
           try
           {
-          cookie =
-            resultEntry.getAttribute("changelogcookie").get(0).iterator().next().toString();
+            cookie =
+              resultEntry.getAttribute("changelogcookie").get(0).iterator().next().toString();
           }
           catch(NullPointerException e)
           {}
         }
       }
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS);
-
       // we expect msg1 + msg4 + msg5
       assertEquals(searchOp.getSearchEntries().size(), 3);
 
+      //
+      // Test startState ("first cookie") of the ECL
+      //
       // --
       ReplicationBroker s1test2 = openReplicationSession(
           DN.decode(TEST_ROOT_DN_STRING2), (short) 1203, 
@@ -852,7 +872,6 @@
           1000, true);
       sleep(500);
 
-      // Test startState ("first cookie") of the ECL
       time = TimeThread.getTime();
       cn = new ChangeNumber(time++, ts++, s1test2.getServerId());
       delMsg =
@@ -887,8 +906,9 @@
       assertTrue(startState.getMaxChangeNumber(s2test2.getServerId()).getSeqnum()==2);
       assertTrue(startState.getMaxChangeNumber(s1test2.getServerId()).getSeqnum()==6);
 
-      // Test lastState ("last cookie") of the ECL
-      // create an ECL sessionm and request lastCookie
+      //
+      // Test lastExternalChangelogCookie attribute of the ECL
+      //
       ExternalChangeLogSessionImpl session = 
         new ExternalChangeLogSessionImpl(replicationServer);
       MultiDomainServerState expectedLastCookie =
@@ -911,13 +931,12 @@
           false, // Types only
           LDAPFilter.decode("(objectclass=*)"),
           lastcookieattribute,
-          null,
+          NO_CONTROL,
           null);
-            
+
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
           searchOp.getErrorMessage().toString()
           + searchOp.getAdditionalLogMessage());
-      
       cookie = "";
       entries = searchOp.getSearchEntries();
       if (entries != null)
@@ -943,24 +962,23 @@
       s1test2.stop();
       s2test.stop();
       s2test2.stop();
-      
+
       removeTestBackend2(backend2);
     }
     catch(Exception e)
     {
-      debugInfo(tn, "Ending test ECLDirectMsg with exception:\n"
+      fail("Ending test " + tn + "with exception:\n"
           +  stackTraceToSingleLineString(e));      
-      assertTrue(e == null);
     }
     debugInfo(tn, "Ending test successfully");
   }
 
   // simple update to be received
-  private void ECLDirectAllOps()
+  private void ECLAllOps()
   {
-    String tn = "ECLDirectAllOps";
+    String tn = "ECLAllOps";
     debugInfo(tn, "Starting test\n\n");
- 
+
     try
     {
       LDIFWriter ldifWriter = getLDIFWriter();
@@ -972,6 +990,12 @@
           1000, true);
       int ts = 1;
 
+      // Creates broker on o=test2
+      ReplicationBroker server02 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING2), (short) 1202, 
+          100, replicationServerPort,
+          1000, true); 
+
       String user1entryUUID = "11111111-1111-1111-1111-111111111111";
       String baseUUID       = "22222222-2222-2222-2222-222222222222";
 
@@ -1000,6 +1024,15 @@
       server01.publish(addMsg);
       debugInfo(tn, " publishes " + addMsg.getChangeNumber());
 
+      // Publish DEL
+      /*
+      ChangeNumber cn12 = new ChangeNumber(TimeThread.getTime(), ts++, (short)1202);
+      DeleteMsg delMsg2 =
+        new DeleteMsg("uid="+tn+"12," + TEST_ROOT_DN_STRING2, cn12, tn+"uuid12");
+      server02.publish(delMsg2);
+      debugInfo(tn, " publishes " + delMsg2.getChangeNumber());
+      */
+
       // Publish MOD
       ChangeNumber cn3 = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
       Attribute attr1 = Attributes.create("description", "new value");
@@ -1024,12 +1057,11 @@
       ModifyDNMsg modDNMsg = new ModifyDNMsg(localOp);
       server01.publish(modDNMsg);
       debugInfo(tn, " publishes " + modDNMsg.getChangeNumber());
-      sleep(600);
+      sleep(1000);
 
       String cookie= "";
 
       // search on 'cn=changelog'
-      debugInfo(tn, "STEP 1 - from empty cookie("+cookie+")");
       LinkedHashSet<String> attributes = new LinkedHashSet<String>();
       attributes.add("+");
       attributes.add("*");
@@ -1040,28 +1072,28 @@
       ArrayList<Control> controls = new ArrayList<Control>(0);
       controls.add(control);
 
+      debugInfo(tn, "Search with cookie=" + cookie + "\" filter=" + 
+          "(targetdn=*"+tn+"*,o=test)");
       InternalSearchOperation searchOp = 
         connection.processSearch(
             ByteString.valueOf("cn=changelog"),
-          SearchScope.WHOLE_SUBTREE,
-          DereferencePolicy.NEVER_DEREF_ALIASES, 
-          0, // Size limit
-          0, // Time limit
-          false, // Types only
-          LDAPFilter.decode("(targetdn=*"+tn+"*,o=test)"),
-          attributes,
-          controls,
-          null);
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 
+            0, // Size limit
+            0, // Time limit
+            false, // Types only
+            LDAPFilter.decode("(targetdn=*"+tn+"*,o=test)"),
+            attributes,
+            controls,
+            null);
       sleep(500);
 
       // test success
       assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
           searchOp.getErrorMessage().toString());
-      
       // test 4 entries returned
-      LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
-      // 4 entries expected
       assertEquals(searchOp.getSearchEntries().size(), 4);
+      LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
       if (entries != null)
       {
         int i=0;
@@ -1073,16 +1105,20 @@
           if (i==1)
           {
             // check the DEL entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn=" + cn1 + "," + TEST_ROOT_DN_STRING + ",cn=changelog"));
             checkValue(resultEntry,"replicationcsn",cn1.toString());
             checkValue(resultEntry,"replicaidentifier","1201");
             checkValue(resultEntry,"targetdn","uid="+tn+"1," + TEST_ROOT_DN_STRING);
             checkValue(resultEntry,"changetype","delete");
-            checkValue(resultEntry,"changelogcookie","o=test:"+cn1.toString()+";");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn1.toString()+";o=test2:;");
             checkValue(resultEntry,"targetentryuuid",tn+"uuid1");
-            checkValue(resultEntry,"changenumber","-1");
+            checkValue(resultEntry,"changenumber","0");
           } else if (i==2)
           {
             // check the ADD entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn=" + cn2 + "," + TEST_ROOT_DN_STRING + ",cn=changelog"));
             String expectedValue1 = "objectClass: domain\nobjectClass: top\n" +
             "entryUUID: 11111111-1111-1111-1111-111111111111\n\n";
             String expectedValue2 = "entryUUID: 11111111-1111-1111-1111-111111111111\n" +
@@ -1092,12 +1128,14 @@
             checkValue(resultEntry,"replicaidentifier","1201");
             checkValue(resultEntry,"targetdn","uid="+tn+"2," + TEST_ROOT_DN_STRING);
             checkValue(resultEntry,"changetype","add");
-            checkValue(resultEntry,"changelogcookie","o=test:"+cn2.toString()+";");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn2.toString()+";o=test2:;");
             checkValue(resultEntry,"targetentryuuid",user1entryUUID);
-            checkValue(resultEntry,"changenumber","-1");
+            checkValue(resultEntry,"changenumber","0");
           } else if (i==3)
           {
             // check the MOD entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn=" + cn3 + "," + TEST_ROOT_DN_STRING + ",cn=changelog"));
             String expectedValue = "replace: description\n" +
             "description: new value\n-\n";
             checkValue(resultEntry,"changes",expectedValue);
@@ -1105,32 +1143,35 @@
             checkValue(resultEntry,"replicaidentifier","1201");
             checkValue(resultEntry,"targetdn","uid="+tn+"3," + TEST_ROOT_DN_STRING);
             checkValue(resultEntry,"changetype","modify");
-            checkValue(resultEntry,"changelogcookie","o=test:"+cn3.toString()+";");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn3.toString()+";o=test2:;");
             checkValue(resultEntry,"targetentryuuid",tn+"uuid3");
-            checkValue(resultEntry,"changenumber","-1");
+            checkValue(resultEntry,"changenumber","0");
           } else if (i==4)
           {
             // check the MODDN entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn=" + cn4 + "," + TEST_ROOT_DN_STRING + ",cn=changelog"));
             checkValue(resultEntry,"replicationcsn",cn4.toString());
             checkValue(resultEntry,"replicaidentifier","1201");
             checkValue(resultEntry,"targetdn","uid="+tn+"4," + TEST_ROOT_DN_STRING);
             checkValue(resultEntry,"changetype","modrdn");
-            checkValue(resultEntry,"changelogcookie","o=test:"+cn4.toString()+";");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn4.toString()+";o=test2:;");
             checkValue(resultEntry,"targetentryuuid",tn+"uuid4");
-            checkValue(resultEntry,"newrdn","uid=ECLDirectAllOpsnew4");            
+            checkValue(resultEntry,"newrdn","uid=ECLAllOpsnew4");            
             checkValue(resultEntry,"newsuperior",TEST_ROOT_DN_STRING2);
             checkValue(resultEntry,"deleteoldrdn","true");
-            checkValue(resultEntry,"changenumber","-1");
+            checkValue(resultEntry,"changenumber","0");
           }
         }
       }
       server01.stop();
+      if (server02 != null)
+        server02.stop();
     }
     catch(Exception e)
     {
-      debugInfo(tn, "Ending test with exception:\n"
+      fail("Ending test " + tn + " with exception:\n"
           +  stackTraceToSingleLineString(e));      
-      assertTrue(e == null);
     }
     debugInfo(tn, "Ending test with success");
   }
@@ -1140,13 +1181,13 @@
     AttributeValue av = null;
     try
     {
-    List<Attribute> attrs = entry.getAttribute(attrName);
-    Attribute a = attrs.iterator().next();
-    av = a.iterator().next();
-    String encodedValue = av.toString();
+      List<Attribute> attrs = entry.getAttribute(attrName);
+      Attribute a = attrs.iterator().next();
+      av = a.iterator().next();
+      String encodedValue = av.toString();
       assertTrue(encodedValue.equalsIgnoreCase(expectedValue),
-        "In entry " + entry + " attr <" + attrName + "> equals " +
-        av + " instead of expected value " + expectedValue); 
+          "In entry " + entry + " attr <" + attrName + "> equals " +
+          av + " instead of expected value " + expectedValue); 
     }
     catch(Exception e)
     {
@@ -1155,23 +1196,23 @@
           av + " instead of expected value " + expectedValue);       
     }
   }
-  
+
   private static void checkPossibleValues(Entry entry, String attrName, 
       String expectedValue1, String expectedValue2)
   {
     AttributeValue av = null;
     try
     {
-    List<Attribute> attrs = entry.getAttribute(attrName);
-    Attribute a = attrs.iterator().next();
-    av = a.iterator().next();
-    String encodedValue = av.toString();
+      List<Attribute> attrs = entry.getAttribute(attrName);
+      Attribute a = attrs.iterator().next();
+      av = a.iterator().next();
+      String encodedValue = av.toString();
       assertTrue(
           (encodedValue.equalsIgnoreCase(expectedValue1) ||
               encodedValue.equalsIgnoreCase(expectedValue2)),
-        "In entry " + entry + " attr <" + attrName + "> equals " +
-        av + " instead of one of the expected values " + expectedValue1
-        + " or " + expectedValue2); 
+              "In entry " + entry + " attr <" + attrName + "> equals " +
+              av + " instead of one of the expected values " + expectedValue1
+              + " or " + expectedValue2); 
     }
     catch(Exception e)
     {
@@ -1181,13 +1222,14 @@
           + " or " + expectedValue2); 
     }
   }
-  
+
   /**
    * Test persistent search
    */
-  private void ECLDirectPsearch(boolean changesOnly)
+  private void ECLPsearch(boolean changesOnly, boolean compatMode)
   {
-    String tn = "ECLDirectPsearch_" + String.valueOf(changesOnly);
+    String tn = "ECLPsearch_" + String.valueOf(changesOnly) + "_" + 
+      String.valueOf(compatMode);
     debugInfo(tn, "Starting test \n\n");
     Socket s =null;
 
@@ -1217,13 +1259,20 @@
       // Produce update on this suffix
       ChangeNumber cn = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
       DeleteMsg delMsg =
-        new DeleteMsg("uid=" + tn + "1," + TEST_ROOT_DN_STRING, cn, tn+"uuid1");
+        new DeleteMsg("uid=" + tn + "1," + TEST_ROOT_DN_STRING, cn, 
+            "11111111-1112-1113-1114-111111111114");
       debugInfo(tn, " publishing " + delMsg.getChangeNumber());
       server01.publish(delMsg);
       this.sleep(500); // let's be sure the message is in the RS
 
       // Creates cookie control
-      ArrayList<Control> controls = getControls("");
+      String cookie = "";
+      ArrayList<Control> controls = createControls(cookie);
+      if (compatMode)
+      {
+        cookie = null;
+        controls = new ArrayList<Control>(0);
+      }
 
       // Creates psearch control
       HashSet<PersistentSearchChangeType> changeTypes =
@@ -1250,6 +1299,7 @@
             null);
 
       // Connects and bind
+      debugInfo(tn, "Search with cookie=" + cookie + "\"");
       s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
       org.opends.server.tools.LDAPReader r = new org.opends.server.tools.LDAPReader(s);
       LDAPWriter w = new LDAPWriter(s);
@@ -1269,7 +1319,7 @@
       long searchReferences = ldapStatistics.getSearchResultReferences();
       long searchesDone     = ldapStatistics.getSearchResultsDone();
 
-      debugInfo(tn, "Sending the PSearch request filter=(targetDN=*"+tn+"*,o=test)");
+      debugInfo(tn, "Search Persistent filter=(targetDN=*"+tn+"*,o=test)");
       LDAPMessage message;
       message = new LDAPMessage(2, searchRequest, controls);
       w.writeMessage(message);
@@ -1281,21 +1331,24 @@
       if (changesOnly == false)
       {
         // Wait for change 1
-        debugInfo(tn, "Waiting for : INIT search expected to return change 1");
+        debugInfo(tn, "Waiting for init search expected to return change 1");
         searchEntries = 0;
         message = null;
-        
+
         try
         {
           while ((searchEntries<1) && (message = r.readMessage()) != null)
           {
-            debugInfo(tn, "First search returns " + 
+            debugInfo(tn, "Init search Result=" + 
                 message.getProtocolOpType() + message + " " + searchEntries);
             switch (message.getProtocolOpType())
             {
             case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
               searchResultEntry = message.getSearchResultEntryProtocolOp();
               searchEntries++;
+              // FIXME:ECL Double check 10 is really the valid value here.
+              checkValue(searchResultEntry.toSearchResultEntry(),"changenumber",
+                  (compatMode?"10":"0"));
               break;
 
             case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
@@ -1307,7 +1360,6 @@
               assertEquals(
                   searchResultDone.getResultCode(), ResultCode.SUCCESS,
                   searchResultDone.getErrorMessage().toString());
-//            assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
               searchesDone++;
               break;
             }
@@ -1315,23 +1367,24 @@
         }
         catch(Exception e)
         {
-          debugInfo(tn, "INIT search failed with e=" +
-              stackTraceToSingleLineString(e));        
+          fail("init search failed with e=" + stackTraceToSingleLineString(e));        
         }
         debugInfo(tn, "INIT search done with success. searchEntries="
-            + searchEntries + " searchesDone="+ searchesDone);
+            + searchEntries + " #searchesDone="+ searchesDone);
       }
 
       // Produces change 2
       cn = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
       String expectedDn = "uid=" + tn + "2," +  TEST_ROOT_DN_STRING;
-      delMsg = new DeleteMsg(expectedDn, cn, tn + "uuid2");
+      delMsg = new DeleteMsg(expectedDn, cn,
+         "11111111-1112-1113-1114-111111111115");
       debugInfo(tn, " publishing " + delMsg.getChangeNumber());
       server01.publish(delMsg);
+      this.gblCN = cn;
       this.sleep(1000);
 
       debugInfo(tn, delMsg.getChangeNumber() +
-          " published , will wait for new entries (Persist)");
+      " published , psearch will now wait for new entries");
 
       // wait for the 1 new entry
       searchEntries = 0;
@@ -1340,7 +1393,7 @@
       message = null;
       while ((searchEntries<1) && (message = r.readMessage()) != null)
       {
-        debugInfo(tn, "2nd search returns " + 
+        debugInfo(tn, "psearch search  Result=" + 
             message.getProtocolOpType() + message);
         switch (message.getProtocolOpType())
         {
@@ -1364,7 +1417,7 @@
         }
       }
       sleep(1000);
-      
+
       // Check we received change 2
       for (LDAPAttribute a : searchResultEntry.getAttributes())
       {
@@ -1391,16 +1444,172 @@
       // When problem found, we have to re-enable this test.
       if (false)
       {
-    
-      // ACI step
-      debugInfo(tn, "Starting ACI step");
-      s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
-      r = new org.opends.server.tools.LDAPReader(s);
-      w = new LDAPWriter(s);
-      s.setSoTimeout(1500000);
-      bindAsWhoEver(w, r, "toto", "tutu", LDAPResultCode.OPERATIONS_ERROR);
-      
-      searchRequest =
+
+        // ACI step
+        debugInfo(tn, "Starting ACI step");
+        s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
+        r = new org.opends.server.tools.LDAPReader(s);
+        w = new LDAPWriter(s);
+        s.setSoTimeout(1500000);
+        bindAsWhoEver(w, r, "toto", "tutu", LDAPResultCode.OPERATIONS_ERROR);
+
+        searchRequest =
+          new SearchRequestProtocolOp(
+              ByteString.valueOf("cn=changelog"),
+              SearchScope.WHOLE_SUBTREE,
+              DereferencePolicy.NEVER_DEREF_ALIASES,
+              Integer.MAX_VALUE,
+              Integer.MAX_VALUE,
+              false,
+              LDAPFilter.decode("(targetDN=*directpsearch*,o=test)"),
+              null);
+
+        debugInfo(tn, "ACI test : sending search");
+        message = new LDAPMessage(2, searchRequest, createControls(""));
+        w.writeMessage(message);
+
+        searchesDone=0;
+        searchEntries = 0;
+        searchResultEntry = null;
+        searchResultDone = null;
+        while ((searchesDone==0) && (message = r.readMessage()) != null)
+        {
+          debugInfo(tn, "ACI test : message returned " + 
+              message.getProtocolOpType() + message);
+          switch (message.getProtocolOpType())
+          {
+          case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
+            searchResultEntry = message.getSearchResultEntryProtocolOp();
+            //assertTrue(false, "Unexpected entry returned in ACI test of " + tn + searchResultEntry);
+            searchEntries++;
+            break;
+
+          case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
+            searchReferences++;
+            break;
+
+          case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
+            searchResultDone = message.getSearchResultDoneProtocolOp();
+            assertEquals(searchResultDone.getResultCode(), 
+                ResultCode.SUCCESS.getIntValue());
+//          assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
+            searchesDone++;
+            break;
+          }
+        }
+        // search should end with success
+        assertTrue(searchesDone==1);
+        // but returning no entry
+        assertEquals(searchEntries,0, "Bad search entry# in ACI test of " + tn);
+      }      
+
+      try { s.close(); } catch (Exception e) {};
+      sleep(1000);
+    }
+    catch(Exception e)
+    {
+      fail("Test " + tn + " fails with " +  stackTraceToSingleLineString(e));
+    }
+    debugInfo(tn, "Ends test successfully");
+  }
+
+  /**
+   * Test parallel simultaneous psearch with different filters.
+   */
+  private void ECLSimulPsearches()
+  {
+    String tn = "ECLSimulPsearches";
+    debugInfo(tn, "Starting test \n\n");
+    Socket s1, s2, s3 = null;
+    boolean compatMode = false;
+    boolean changesOnly = false;
+
+    // create stats
+    for (ConnectionHandler ch : DirectoryServer.getConnectionHandlers())
+    {
+      if (ch instanceof LDAPConnectionHandler)
+      {
+        LDAPConnectionHandler lch = (LDAPConnectionHandler) ch;
+        if (!lch.useSSL())
+        {
+          ldapStatistics = lch.getStatTracker();
+        }
+      }
+    }
+    assertNotNull(ldapStatistics);
+
+    try
+    {
+      // Create broker on o=test
+      ReplicationBroker server01 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING), (short) 1201, 
+          100, replicationServerPort,
+          1000, true);
+      int ts = 1;
+
+      // Create broker on o=test2
+      ReplicationBroker server02 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING2), (short) 1202, 
+          100, replicationServerPort,
+          1000, true, EMPTY_DN_GENID);
+
+      // Produce update 1
+      ChangeNumber cn1 = 
+        new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+      DeleteMsg delMsg1 =
+        new DeleteMsg("uid=" + tn + "1," + TEST_ROOT_DN_STRING, cn1, 
+            "11111111-1111-1111-1111-111111111111");
+      debugInfo(tn, " publishing " + delMsg1);
+      server01.publish(delMsg1);
+      this.sleep(500); // let's be sure the message is in the RS
+
+      // Produce update 2
+      ChangeNumber cn2 = 
+        new ChangeNumber(TimeThread.getTime(), ts++, (short)1202);
+      DeleteMsg delMsg2 =
+        new DeleteMsg("uid=" + tn + "2," + TEST_ROOT_DN_STRING2, cn2, 
+            "22222222-2222-2222-2222-222222222222");
+      debugInfo(tn, " publishing " + delMsg2);
+      server02.publish(delMsg2);
+      this.sleep(500); // let's be sure the message is in the RS
+
+      // Produce update 3
+      ChangeNumber cn3 = 
+        new ChangeNumber(TimeThread.getTime(), ts++, (short)1202);
+      DeleteMsg delMsg3 =
+        new DeleteMsg("uid=" + tn + "3," + TEST_ROOT_DN_STRING2, cn3, 
+            "33333333-3333-3333-3333-333333333333");
+      debugInfo(tn, " publishing " + delMsg3);
+      server02.publish(delMsg3);
+      this.sleep(500); // let's be sure the message is in the RS
+
+      // Creates cookie control
+      String cookie = "";
+      ArrayList<Control> controls = createControls(cookie);
+      if (compatMode)
+      {
+        cookie = null;
+        controls = new ArrayList<Control>(0);
+      }
+
+      // Creates psearch control
+      HashSet<PersistentSearchChangeType> changeTypes =
+        new HashSet<PersistentSearchChangeType>();
+      changeTypes.add(PersistentSearchChangeType.ADD);
+      changeTypes.add(PersistentSearchChangeType.DELETE);
+      changeTypes.add(PersistentSearchChangeType.MODIFY);
+      changeTypes.add(PersistentSearchChangeType.MODIFY_DN);
+      boolean returnECs = true;
+      PersistentSearchControl persSearchControl = new PersistentSearchControl(
+          changeTypes, changesOnly, returnECs);
+      controls.add(persSearchControl);
+
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      attributes.add("+");
+      attributes.add("*");
+
+      // Creates request 1
+      SearchRequestProtocolOp searchRequest1 =
         new SearchRequestProtocolOp(
             ByteString.valueOf("cn=changelog"),
             SearchScope.WHOLE_SUBTREE,
@@ -1408,26 +1617,255 @@
             Integer.MAX_VALUE,
             Integer.MAX_VALUE,
             false,
-            LDAPFilter.decode("(targetDN=*directpsearch*,o=test)"),
-            null);
+            LDAPFilter.decode("(targetDN=*"+tn+"*,o=test)"),
+            attributes);
 
-      debugInfo(tn, "ACI test : sending search");
-      message = new LDAPMessage(2, searchRequest, getControls(""));
-      w.writeMessage(message);
+      // Creates request 2
+      SearchRequestProtocolOp searchRequest2 =
+        new SearchRequestProtocolOp(
+            ByteString.valueOf("cn=changelog"),
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES,
+            Integer.MAX_VALUE,
+            Integer.MAX_VALUE,
+            false,
+            LDAPFilter.decode("(targetDN=*"+tn+"*,o=test2)"),
+            attributes);
 
-      searchesDone=0;
+      // Creates request 3
+      SearchRequestProtocolOp searchRequest3 =
+        new SearchRequestProtocolOp(
+            ByteString.valueOf("cn=changelog"),
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES,
+            Integer.MAX_VALUE,
+            Integer.MAX_VALUE,
+            false,
+            LDAPFilter.decode("objectclass=*"),
+            attributes);
+
+      // Connects and bind
+      s1 = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
+      org.opends.server.tools.LDAPReader r1 = new org.opends.server.tools.LDAPReader(s1);
+      LDAPWriter w1 = new LDAPWriter(s1);
+      s1.setSoTimeout(1500000);
+      bindAsManager(w1, r1);
+
+      // Connects and bind
+      s2 = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
+      org.opends.server.tools.LDAPReader r2 = new org.opends.server.tools.LDAPReader(s2);
+      LDAPWriter w2 = new LDAPWriter(s2);
+      s2.setSoTimeout(1500000);
+      bindAsManager(w2, r2);
+
+      // Connects and bind
+      s3 = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
+      org.opends.server.tools.LDAPReader r3 = new org.opends.server.tools.LDAPReader(s3);
+      LDAPWriter w3 = new LDAPWriter(s3);
+      s3.setSoTimeout(1500000);
+      bindAsManager(w3, r3);
+
+      // Since we are going to be watching the post-response count, we need to
+      // wait for the server to become idle before kicking off the next request
+      // to ensure that any remaining post-response processing from the previous
+      // operation has completed.
+      assertTrue(DirectoryServer.getWorkQueue().waitUntilIdle(10000));
+
+      InvocationCounterPlugin.resetAllCounters();
+
+      long searchRequests   = ldapStatistics.getSearchRequests();
+      long searchEntries    = ldapStatistics.getSearchResultEntries();
+      long searchReferences = ldapStatistics.getSearchResultReferences();
+      long searchesDone     = ldapStatistics.getSearchResultsDone();
+
+      LDAPMessage message;
+      message = new LDAPMessage(2, searchRequest1, controls);
+      w1.writeMessage(message);
+      this.sleep(500);
+
+      message = new LDAPMessage(2, searchRequest2, controls);
+      w2.writeMessage(message);
+      this.sleep(500);
+
+      message = new LDAPMessage(2, searchRequest3, controls);
+      w3.writeMessage(message);
+      this.sleep(500);
+
+      SearchResultEntryProtocolOp searchResultEntry = null;
+      SearchResultDoneProtocolOp searchResultDone = null;
+
+      if (changesOnly == false)
+      {
+        debugInfo(tn, "Search1  Persistent filter="+searchRequest1.getFilter().toString()
+                  + " expected to return change " + cn1);
+        searchEntries = 0;
+        message = null;
+
+        try
+        {
+          while ((searchEntries<1) && (message = r1.readMessage()) != null)
+          {
+            debugInfo(tn, "Search1 Result=" + 
+                message.getProtocolOpType() + " " + message);
+            switch (message.getProtocolOpType())
+            {
+            case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
+              searchResultEntry = message.getSearchResultEntryProtocolOp();
+              searchEntries++;
+              if (searchEntries==1)
+              {
+                checkValue(searchResultEntry.toSearchResultEntry(),"replicationcsn",cn1.toString());
+                checkValue(searchResultEntry.toSearchResultEntry(),"changenumber",
+                    (compatMode?"10":"0"));
+              }
+              break;
+
+            case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
+              searchReferences++;
+              break;
+
+            case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
+              searchResultDone = message.getSearchResultDoneProtocolOp();
+              assertEquals(
+                  searchResultDone.getResultCode(), ResultCode.SUCCESS,
+                  searchResultDone.getErrorMessage().toString());
+              searchesDone++;
+              break;
+            }
+          }
+        }
+        catch(Exception e)
+        {
+          fail("Search1 failed with e=" + stackTraceToSingleLineString(e));        
+        }
+        debugInfo(tn, "Search1 done with success. searchEntries="
+            + searchEntries + " #searchesDone="+ searchesDone);
+        
+        searchEntries = 0;
+        message = null;
+        try
+        {
+          debugInfo(tn, "Search 2  Persistent filter="+searchRequest2.getFilter().toString()
+              + " expected to return change " + cn2 + " & " + cn3);
+          while ((searchEntries<2) && (message = r2.readMessage()) != null)
+          {
+            debugInfo(tn, "Search 2 Result=" + 
+                message.getProtocolOpType() + message);
+            switch (message.getProtocolOpType())
+            {
+            case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
+              searchResultEntry = message.getSearchResultEntryProtocolOp();
+              searchEntries++;
+              checkValue(searchResultEntry.toSearchResultEntry(),"changenumber",
+                  (compatMode?"10":"0"));
+              break;
+
+            case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
+              searchReferences++;
+              break;
+
+            case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
+              searchResultDone = message.getSearchResultDoneProtocolOp();
+              assertEquals(
+                  searchResultDone.getResultCode(), ResultCode.SUCCESS,
+                  searchResultDone.getErrorMessage().toString());
+              searchesDone++;
+              break;
+            }
+          }
+        }
+        catch(Exception e)
+        {
+          fail("Search2 failed with e=" + stackTraceToSingleLineString(e));        
+        }
+        debugInfo(tn, "Search2 done with success. searchEntries="
+            + searchEntries + " #searchesDone="+ searchesDone);
+
+        
+        searchEntries = 0;
+        message = null;
+        try
+        {
+          debugInfo(tn, "Search3  Persistent filter="+searchRequest3.getFilter().toString()
+              + " expected to return change top + " + cn1 + " & " + cn2 + " & " + cn3);
+          while ((searchEntries<4) && (message = r3.readMessage()) != null)
+          {
+            debugInfo(tn, "Search3 Result=" + 
+                message.getProtocolOpType() + " " + message);
+
+            switch (message.getProtocolOpType())
+            {
+            case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
+              searchResultEntry = message.getSearchResultEntryProtocolOp();
+              searchEntries++;
+              break;
+
+            case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
+              searchReferences++;
+              break;
+
+            case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
+              searchResultDone = message.getSearchResultDoneProtocolOp();
+              assertEquals(
+                  searchResultDone.getResultCode(), ResultCode.SUCCESS,
+                  searchResultDone.getErrorMessage().toString());
+              searchesDone++;
+              break;
+            }
+          }
+        }
+        catch(Exception e)
+        {
+          fail("Search3 failed with e=" + stackTraceToSingleLineString(e));        
+        }
+        debugInfo(tn, "Search3 done with success. searchEntries="
+            + searchEntries + " #searchesDone="+ searchesDone);
+
+      }
+
+      // Produces additional change
+      ChangeNumber cn11 = new ChangeNumber(TimeThread.getTime(), 11, (short)1201);
+      String expectedDn11 = "uid=" + tn + "11," +  TEST_ROOT_DN_STRING;
+      DeleteMsg delMsg11 = new DeleteMsg(expectedDn11, cn11,
+         "44444444-4444-4444-4444-444444444444");
+      debugInfo(tn, " publishing " + delMsg11);
+      server01.publish(delMsg11);
+      this.sleep(500);
+      debugInfo(tn, delMsg11.getChangeNumber() + " published additionally ");
+
+      // Produces additional change
+      ChangeNumber cn12 = new ChangeNumber(TimeThread.getTime(), 12, (short)1202);
+      String expectedDn12 = "uid=" + tn + "12," +  TEST_ROOT_DN_STRING2;
+      DeleteMsg delMsg12 = new DeleteMsg(expectedDn12, cn12,
+         "55555555-5555-5555-5555-555555555555");
+      debugInfo(tn, " publishing " + delMsg12 );
+      server02.publish(delMsg12);
+      this.sleep(500);
+      debugInfo(tn, delMsg12.getChangeNumber()  + " published additionally ");
+
+      // Produces additional change
+      ChangeNumber cn13 = new ChangeNumber(TimeThread.getTime(), 13, (short)1202);
+      String expectedDn13 = "uid=" + tn + "13," +  TEST_ROOT_DN_STRING2;
+      DeleteMsg delMsg13 = new DeleteMsg(expectedDn13, cn13,
+         "66666666-6666-6666-6666-666666666666");
+      debugInfo(tn, " publishing " + delMsg13);
+      server02.publish(delMsg13);
+      this.sleep(500);
+      debugInfo(tn, delMsg13.getChangeNumber()  + " published additionally ");
+
+      // wait 11
       searchEntries = 0;
       searchResultEntry = null;
       searchResultDone = null;
-      while ((searchesDone==0) && (message = r.readMessage()) != null)
+      message = null;
+      while ((searchEntries<1) && (message = r1.readMessage()) != null)
       {
-        debugInfo(tn, "ACI test : message returned " + 
-            message.getProtocolOpType() + message);
+        debugInfo(tn, "Search 11 Result=" + 
+            message.getProtocolOpType() + " " + message);
         switch (message.getProtocolOpType())
         {
         case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
           searchResultEntry = message.getSearchResultEntryProtocolOp();
-          //assertTrue(false, "Unexpected entry returned in ACI test of " + tn + searchResultEntry);
           searchEntries++;
           break;
 
@@ -1437,27 +1875,141 @@
 
         case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
           searchResultDone = message.getSearchResultDoneProtocolOp();
-          assertEquals(searchResultDone.getResultCode(), 
-              ResultCode.SUCCESS.getIntValue());
+          assertEquals(
+              searchResultDone.getResultCode(), ResultCode.SUCCESS,
+              searchResultDone.getErrorMessage().toString());
 //        assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
           searchesDone++;
           break;
         }
       }
-      // search should end with success
-      assertTrue(searchesDone==1);
-      // but returning no entry
-      assertEquals(searchEntries,0, "Bad search entry# in ACI test of " + tn);
-      }      
+      sleep(1000);
+      /*
+      // Check we received change 11
+      for (LDAPAttribute a : searchResultEntry.getAttributes())
+      {
+        if (a.getAttributeType().equalsIgnoreCase("targetDN"))
+        {
+          for (ByteString av : a.getValues())
+          {
+            assertTrue(av.toString().equalsIgnoreCase(expectedDn11),
+                "Entry returned by psearch11 is " + av.toString() +
+                " when expected is " + expectedDn11);
+          }
+        }
+      }
+      */
+      debugInfo(tn, "Search 1 successfully receives additional changes");
 
-      try { s.close(); } catch (Exception e) {};
+      // wait 12 & 13
+      searchEntries = 0;
+      searchResultEntry = null;
+      searchResultDone = null;
+      message = null;
+      while ((searchEntries<2) && (message = r2.readMessage()) != null)
+      {
+        debugInfo(tn, "psearch search 12 Result=" + 
+            message.getProtocolOpType() + " " + message);
+        switch (message.getProtocolOpType())
+        {
+        case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
+          searchResultEntry = message.getSearchResultEntryProtocolOp();
+          searchEntries++;
+          break;
+
+        case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
+          searchReferences++;
+          break;
+
+        case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
+          searchResultDone = message.getSearchResultDoneProtocolOp();
+          assertEquals(
+              searchResultDone.getResultCode(), ResultCode.SUCCESS,
+              searchResultDone.getErrorMessage().toString());
+//        assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
+          searchesDone++;
+          break;
+        }
+      }
+      sleep(1000);
+      /*
+      // Check we received change 12
+      for (LDAPAttribute a : searchResultEntry.getAttributes())
+      {
+        if (a.getAttributeType().equalsIgnoreCase("targetDN"))
+        {
+          for (ByteString av : a.getValues())
+          {
+            assertTrue(av.toString().equalsIgnoreCase(expectedDn12),
+                "Entry returned by psearch 12 is " + av.toString() +
+                " when expected is " + expectedDn12);
+          }
+        }
+      }
+      */
+      debugInfo(tn, "Search 2 successfully receives additional changes");
+
+      // wait 11 & 12 & 13
+      searchEntries = 0;
+      searchResultEntry = null;
+      searchResultDone = null;
+      message = null;
+      while ((searchEntries<3) && (message = r3.readMessage()) != null)
+      {
+        debugInfo(tn, "psearch search 13 Result=" + 
+            message.getProtocolOpType() + " " + message);
+        switch (message.getProtocolOpType())
+        {
+        case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
+          searchResultEntry = message.getSearchResultEntryProtocolOp();
+          searchEntries++;
+          break;
+
+        case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
+          searchReferences++;
+          break;
+
+        case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
+          searchResultDone = message.getSearchResultDoneProtocolOp();
+          assertEquals(
+              searchResultDone.getResultCode(), ResultCode.SUCCESS,
+              searchResultDone.getErrorMessage().toString());
+//        assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
+          searchesDone++;
+          break;
+        }
+      }
+      sleep(1000);
+
+      // Check we received change 13
+      for (LDAPAttribute a : searchResultEntry.getAttributes())
+      {
+        if (a.getAttributeType().equalsIgnoreCase("targetDN"))
+        {
+          for (ByteString av : a.getValues())
+          {
+            assertTrue(av.toString().equalsIgnoreCase(expectedDn13),
+                "Entry returned by psearch 13 is " + av.toString() +
+                " when expected is " + expectedDn13);
+          }
+        }
+      }
+      debugInfo(tn, "Search 3 successfully receives additional changes");
+
+      server01.stop();
+      server02.stop();
+
+      try { s1.close(); } catch (Exception e) {};
+      try { s2.close(); } catch (Exception e) {};
+      try { s3.close(); } catch (Exception e) {};
+
       sleep(1000);
     }
     catch(Exception e)
     {
-      assertTrue(e==null, stackTraceToSingleLineString(e));
+      fail("Test " + tn + " fails with " +  stackTraceToSingleLineString(e));
     }
-    debugInfo(tn, "Ends test successfuly");
+    debugInfo(tn, "Ends test successfully");
   }
 
   // utility - bind as required
@@ -1522,7 +2074,7 @@
     TestCaseUtils.dsconfig(
         "delete-replication-server",
         "--provider-name", "Multimaster Synchronization");
-        */
+     */
     replicationServer = null;
   }
   /**
@@ -1549,7 +2101,7 @@
     {
       logError(Message.raw(Category.SYNC, Severity.NOTICE,
           "** TEST " + tn + " ** " + s));
-      TRACER.debugInfo("** TEST " + tn + " ** " + s);
+      //TRACER.debugInfo("** TEST " + tn + " ** " + s);
     }
   }
 
@@ -1558,7 +2110,7 @@
    */
   private static Backend initializeTestBackend2(boolean createBaseEntry)
   throws IOException, InitializationException, ConfigException,
-         DirectoryException
+  DirectoryException
   {
 
     DN baseDN = DN.decode(TEST_ROOT_DN_STRING2);
@@ -1590,11 +2142,849 @@
     }
     return memoryBackend;
   }
-  
+
   private static void removeTestBackend2(Backend backend)
   {
     MemoryBackend memoryBackend = (MemoryBackend)backend;
     memoryBackend.finalizeBackend();
     DirectoryServer.deregisterBackend(memoryBackend);
   }
+
+  //=======================================================
+  private void ChangeTimeHeartbeatTest()
+  {
+    String tn = "ChangeTimeHeartbeatTest";
+    debugInfo(tn, "Starting test");
+
+    try
+    {
+      // Initialize a second test backend
+      Backend backend2 = initializeTestBackend2(true);
+
+      // --
+      ReplicationBroker s1test = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING), (short) 1201, 
+          100, replicationServerPort,
+          1000, true);
+
+      ReplicationBroker s2test2 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING2), (short) 1202, 
+          100, replicationServerPort,
+          1000, true, EMPTY_DN_GENID);
+      sleep(500);
+
+      // Produce updates
+      long time = TimeThread.getTime();
+      int ts = 1;
+      ChangeNumber cn = new ChangeNumber(time, ts++, s1test.getServerId());
+      DeleteMsg delMsg =
+        new DeleteMsg("uid="+tn+"1," + TEST_ROOT_DN_STRING, cn, tn+"uuid1");
+      s1test.publish(delMsg);
+      debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+
+      cn = new ChangeNumber(time++, ts++, s2test2.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"2," + TEST_ROOT_DN_STRING2, cn, tn+"uuid2");
+      s2test2.publish(delMsg);
+      debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+
+      ChangeNumber cn3 = new ChangeNumber(time++, ts++, s2test2.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"3," + TEST_ROOT_DN_STRING2, cn3, tn+"uuid3");
+      s2test2.publish(delMsg);
+      debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+
+      cn = new ChangeNumber(time++, ts++, s1test.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"4," + TEST_ROOT_DN_STRING, cn, tn+"uuid4");
+      s1test.publish(delMsg);
+      debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+      sleep(500);
+
+      // --
+      ReplicationBroker s1test2 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING2), (short) 1203, 
+          100, replicationServerPort,
+          1000, true, EMPTY_DN_GENID);
+
+      ReplicationBroker s2test = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING), (short) 1204, 
+          100, replicationServerPort,
+          1000, true);
+      sleep(500);
+
+      // Test startState ("first cookie") of the ECL
+      time = TimeThread.getTime();
+      cn = new ChangeNumber(time++, ts++, s1test2.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"6," + TEST_ROOT_DN_STRING2, cn, tn+"uuid6");
+      s1test2.publish(delMsg);
+
+      cn = new ChangeNumber(time++, ts++, s2test.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"7," + TEST_ROOT_DN_STRING, cn, tn+"uuid7");
+      s2test.publish(delMsg);
+
+      ChangeNumber cn8 = new ChangeNumber(time++, ts++, s1test2.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"8," + TEST_ROOT_DN_STRING2, cn8, tn+"uuid8");
+      s1test2.publish(delMsg);
+
+      ChangeNumber cn9 = new ChangeNumber(time++, ts++, s2test.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"9," + TEST_ROOT_DN_STRING, cn9, tn+"uuid9");
+      s2test.publish(delMsg);
+      sleep(500);
+
+      ReplicationServerDomain rsd1 =
+        replicationServer.getReplicationServerDomain(TEST_ROOT_DN_STRING, false);
+      rsd1.getDbServerState();
+      rsd1.getChangeTimeHeartbeatState();
+      debugInfo(tn, " DbServerState=" + rsd1.getDbServerState()
+          + " ChangeTimeHeartBeatState=" + rsd1.getChangeTimeHeartbeatState()
+          + " eligibleCN=" + rsd1.getEligibleCN());
+      // FIXME:ECL Enable this test by adding an assert on the right value
+
+      ReplicationServerDomain rsd2 =
+        replicationServer.getReplicationServerDomain(TEST_ROOT_DN_STRING2, false);
+      rsd2.getDbServerState();
+      rsd2.getChangeTimeHeartbeatState();
+      debugInfo(tn, " DbServerState=" + rsd2.getDbServerState()
+          + " ChangeTimeHeartBeatState=" + rsd2.getChangeTimeHeartbeatState()
+          + " eligibleCN=" + rsd2.getEligibleCN());
+      // FIXME:ECL Enable this test by adding an assert on the right value
+
+      s1test.stop();
+      s1test2.stop();
+      s2test.stop();
+      s2test2.stop();
+
+      removeTestBackend2(backend2);
+    }
+    catch(Exception e)
+    {
+      fail("Ending test " + tn + " with exception:"
+          +  stackTraceToSingleLineString(e));      
+    }
+    debugInfo(tn, "Ending test successfully");
+  }
+
+  /**
+   * From embedded ECL (no remote session)
+   * With empty RS, simple search should return only root entry.
+   */
+  private void ECLCompatEmpty()
+  {
+    String tn = "ECLCompatEmpty";
+    debugInfo(tn, "Starting test\n\n");
+
+    try
+    {
+      // search on 'cn=changelog'
+      String filter = "(objectclass=*)";
+      debugInfo(tn, " Search: " + filter);
+      InternalSearchOperation op = connection.processSearch(
+          ByteString.valueOf("cn=changelog"),
+          SearchScope.WHOLE_SUBTREE,
+          LDAPFilter.decode(filter));
+
+      // success
+      assertEquals(
+          op.getResultCode(), ResultCode.SUCCESS,
+          op.getErrorMessage().toString());
+
+      // root entry returned
+      assertEquals(op.getEntriesSent(), 1);
+      debugInfo(tn, "Ending test successfully");
+    }
+    catch(LDAPException e)
+    {
+      fail("Ending test " + tn + " with exception="
+          +  stackTraceToSingleLineString(e));      
+    }
+  }
+
+  private void ECLCompatWriteReadAllOps(int firstDraftChangeNumber)
+  {
+    String tn = "ECLCompatWriteReadAllOps/" + String.valueOf(firstDraftChangeNumber);
+    debugInfo(tn, "Starting test\n\n");
+
+    try
+    {
+      LDIFWriter ldifWriter = getLDIFWriter();
+
+      // Creates broker on o=test
+      ReplicationBroker server01 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING), (short) 1201, 
+          100, replicationServerPort,
+          1000, true);
+      int ts = 1;
+
+      String user1entryUUID = "11111111-1112-1113-1114-111111111115";
+      String baseUUID       = "22222222-2222-2222-2222-222222222222";
+
+
+      // Publish DEL
+      ChangeNumber cn1 = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+      DeleteMsg delMsg =
+        new DeleteMsg("uid="+tn+"1," + TEST_ROOT_DN_STRING, cn1, 
+            user1entryUUID);
+      server01.publish(delMsg);
+      debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+
+      // Publish ADD
+      gblCN = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+      String lentry = new String(
+          "dn: uid="+tn+"2," + TEST_ROOT_DN_STRING + "\n"
+          + "objectClass: top\n" 
+          + "objectClass: domain\n"
+          + "entryUUID: "+user1entryUUID+"\n");
+      Entry entry = TestCaseUtils.entryFromLdifString(lentry);
+      AddMsg addMsg = new AddMsg(
+          gblCN, 
+          "uid="+tn+"2," + TEST_ROOT_DN_STRING,
+          user1entryUUID, 
+          baseUUID, 
+          entry.getObjectClassAttribute(), 
+          entry.getAttributes(), 
+          new ArrayList<Attribute>());
+      server01.publish(addMsg);
+      debugInfo(tn, " publishes " + addMsg.getChangeNumber());
+
+      // Publish MOD
+      ChangeNumber cn3 = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+      Attribute attr1 = Attributes.create("description", "new value");
+      Modification mod1 = new Modification(ModificationType.REPLACE, attr1);
+      List<Modification> mods = new ArrayList<Modification>();
+      mods.add(mod1);
+      ModifyMsg modMsg = new ModifyMsg(cn3, DN
+          .decode("uid="+tn+"3," + TEST_ROOT_DN_STRING), mods, user1entryUUID);
+      server01.publish(modMsg);
+      debugInfo(tn, " publishes " + modMsg.getChangeNumber());
+
+      // Publish modDN
+      ChangeNumber cn4 = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+      ModifyDNOperationBasis op = new ModifyDNOperationBasis(connection, 1, 1, null,
+          DN.decode("uid="+tn+"4," + TEST_ROOT_DN_STRING), // entryDN
+          RDN.decode("uid="+tn+"new4"), // new rdn
+          true,  // deleteoldrdn
+          DN.decode(TEST_ROOT_DN_STRING2)); // new superior
+      op.setAttachment(SYNCHROCONTEXT, new ModifyDnContext(cn4, user1entryUUID,
+      "newparentId"));
+      LocalBackendModifyDNOperation localOp = new LocalBackendModifyDNOperation(op);
+      ModifyDNMsg modDNMsg = new ModifyDNMsg(localOp);
+      server01.publish(modDNMsg);
+      debugInfo(tn, " publishes " + modDNMsg.getChangeNumber());
+      sleep(1000);
+
+      // search on 'cn=changelog'
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      attributes.add("+");
+      attributes.add("*");
+
+      String filter = "(targetdn=*"+tn.toLowerCase()+"*,o=test)";
+      debugInfo(tn, " Search: " + filter);
+      InternalSearchOperation searchOp = 
+        connection.processSearch(
+            ByteString.valueOf("cn=changelog"),
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 
+            0, // Size limit
+            0, // Time limit
+            false, // Types only
+            LDAPFilter.decode(filter),
+            attributes,
+            NO_CONTROL,
+            null);
+      sleep(500);
+
+      // test success
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+          searchOp.getErrorMessage().toString());
+
+      // test 4 entries returned
+      LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
+      // 4 entries expected
+      assertEquals(searchOp.getSearchEntries().size(), 4);
+      if (entries != null)
+      {
+        int i=0;
+        for (SearchResultEntry resultEntry : entries)
+        {
+          i++;
+          debugInfo(tn, "Result entry returned:" + resultEntry.toLDIFString());
+          ldifWriter.writeEntry(resultEntry);
+          if (i==1)
+          {
+            // check the DEL entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn="+String.valueOf(firstDraftChangeNumber+0)+",cn=changelog"));
+            checkValue(resultEntry,"replicationcsn",cn1.toString());
+            checkValue(resultEntry,"replicaidentifier","1201");
+            checkValue(resultEntry,"targetdn","uid="+tn+"1," + TEST_ROOT_DN_STRING);
+            checkValue(resultEntry,"changetype","delete");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn1.toString()+";o=test2:;");
+            checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+            checkValue(resultEntry,"changenumber",String.valueOf(firstDraftChangeNumber+0));
+            checkValue(resultEntry,"targetuniqueid","11111111-11121113-11141111-11111115");
+          } else if (i==2)
+          {
+            // check the ADD entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn="+String.valueOf(firstDraftChangeNumber+1)+",cn=changelog"));
+            String expectedValue1 = "objectClass: domain\nobjectClass: top\n" +
+            "entryUUID: "+user1entryUUID+"\n\n";
+            String expectedValue2 = "entryUUID: "+user1entryUUID+"\n" +
+            "objectClass: domain\nobjectClass: top\n\n";
+            checkPossibleValues(resultEntry,"changes",expectedValue1, expectedValue2);
+            checkValue(resultEntry,"replicationcsn",gblCN.toString());
+            checkValue(resultEntry,"replicaidentifier","1201");
+            checkValue(resultEntry,"targetdn","uid="+tn+"2," + TEST_ROOT_DN_STRING);
+            checkValue(resultEntry,"changetype","add");
+            checkValue(resultEntry,"changelogcookie","o=test:"+gblCN.toString()+";o=test2:;");
+            checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+            checkValue(resultEntry,"changenumber",String.valueOf(firstDraftChangeNumber+1));
+          } else if (i==3)
+          {
+            // check the MOD entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn="+String.valueOf(firstDraftChangeNumber+2)+",cn=changelog"));
+            String expectedValue = "replace: description\n" +
+            "description: new value\n-\n";
+            checkValue(resultEntry,"changes",expectedValue);
+            checkValue(resultEntry,"replicationcsn",cn3.toString());
+            checkValue(resultEntry,"replicaidentifier","1201");
+            checkValue(resultEntry,"targetdn","uid="+tn+"3," + TEST_ROOT_DN_STRING);
+            checkValue(resultEntry,"changetype","modify");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn3.toString()+";o=test2:;");
+            checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+            checkValue(resultEntry,"changenumber",String.valueOf(firstDraftChangeNumber+2));
+          } else if (i==4)
+          {
+            // check the MODDN entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn="+String.valueOf(firstDraftChangeNumber+3)+",cn=changelog"));
+            checkValue(resultEntry,"replicationcsn",cn4.toString());
+            checkValue(resultEntry,"replicaidentifier","1201");
+            checkValue(resultEntry,"targetdn","uid="+tn+"4," + TEST_ROOT_DN_STRING);
+            checkValue(resultEntry,"changetype","modrdn");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn4.toString()+";o=test2:;");
+            checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+            checkValue(resultEntry,"newrdn","uid="+tn+"new4");            
+            checkValue(resultEntry,"newsuperior",TEST_ROOT_DN_STRING2);
+            checkValue(resultEntry,"deleteoldrdn","true");
+            checkValue(resultEntry,"changenumber",String.valueOf(firstDraftChangeNumber+3));
+          }
+        }
+      }
+      server01.stop();
+
+      filter = "(&(targetdn=*"+tn.toLowerCase()+"*,o=test)(&(changenumber>="+
+      firstDraftChangeNumber+")(changenumber<="+(firstDraftChangeNumber+3)+")))";
+      debugInfo(tn, " Search: " + filter);
+      searchOp = 
+        connection.processSearch(
+            ByteString.valueOf("cn=changelog"),
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 
+            0, // Size limit
+            0, // Time limit
+            false, // Types only
+            LDAPFilter.decode(filter),
+            attributes,
+            NO_CONTROL,
+            null);
+      sleep(500);
+
+      // test success
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+          searchOp.getErrorMessage().toString());
+
+      entries = searchOp.getSearchEntries();
+
+      if (entries != null)
+      {
+        int i=0;
+        for (SearchResultEntry resultEntry : entries)
+        {
+          i++;
+          debugInfo(tn, "Result entry returned:" + resultEntry.toLDIFString());
+          ldifWriter.writeEntry(resultEntry);
+          if (i==1)
+          {
+            // check the DEL entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn="+String.valueOf(firstDraftChangeNumber+0)+",cn=changelog"));
+            checkValue(resultEntry,"replicationcsn",cn1.toString());
+            checkValue(resultEntry,"replicaidentifier","1201");
+            checkValue(resultEntry,"targetdn","uid="+tn+"1," + TEST_ROOT_DN_STRING);
+            checkValue(resultEntry,"changetype","delete");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn1.toString()+";o=test2:;");
+            checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+            checkValue(resultEntry,"changenumber",String.valueOf(firstDraftChangeNumber+0));
+            checkValue(resultEntry,"targetuniqueid","11111111-11121113-11141111-11111115");
+          } else if (i==2)
+          {
+            // check the ADD entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn="+String.valueOf(firstDraftChangeNumber+1)+",cn=changelog"));
+            String expectedValue1 = "objectClass: domain\nobjectClass: top\n" +
+            "entryUUID: "+user1entryUUID+"\n\n";
+            String expectedValue2 = "entryUUID: "+user1entryUUID+"\n" +
+            "objectClass: domain\nobjectClass: top\n\n";
+            checkPossibleValues(resultEntry,"changes",expectedValue1, expectedValue2);
+            checkValue(resultEntry,"replicationcsn",gblCN.toString());
+            checkValue(resultEntry,"replicaidentifier","1201");
+            checkValue(resultEntry,"targetdn","uid="+tn+"2," + TEST_ROOT_DN_STRING);
+            checkValue(resultEntry,"changetype","add");
+            checkValue(resultEntry,"changelogcookie","o=test:"+gblCN.toString()+";o=test2:;");
+            checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+            checkValue(resultEntry,"changenumber",String.valueOf(firstDraftChangeNumber+1));
+          } else if (i==3)
+          {
+            // check the MOD entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn="+String.valueOf(firstDraftChangeNumber+2)+",cn=changelog"));
+            String expectedValue = "replace: description\n" +
+            "description: new value\n-\n";
+            checkValue(resultEntry,"changes",expectedValue);
+            checkValue(resultEntry,"replicationcsn",cn3.toString());
+            checkValue(resultEntry,"replicaidentifier","1201");
+            checkValue(resultEntry,"targetdn","uid="+tn+"3," + TEST_ROOT_DN_STRING);
+            checkValue(resultEntry,"changetype","modify");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn3.toString()+";o=test2:;");
+            checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+            checkValue(resultEntry,"changenumber",String.valueOf(firstDraftChangeNumber+2));
+          } else if (i==4)
+          {
+            // check the MODDN entry has the right content
+            assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+                "cn="+String.valueOf(firstDraftChangeNumber+3)+",cn=changelog"));
+            checkValue(resultEntry,"replicationcsn",cn4.toString());
+            checkValue(resultEntry,"replicaidentifier","1201");
+            checkValue(resultEntry,"targetdn","uid="+tn+"4," + TEST_ROOT_DN_STRING);
+            checkValue(resultEntry,"changetype","modrdn");
+            checkValue(resultEntry,"changelogcookie","o=test:"+cn4.toString()+";o=test2:;");
+            checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+            checkValue(resultEntry,"newrdn","uid="+tn+"new4");            
+            checkValue(resultEntry,"newsuperior",TEST_ROOT_DN_STRING2);
+            checkValue(resultEntry,"deleteoldrdn","true");
+            checkValue(resultEntry,"changenumber",String.valueOf(firstDraftChangeNumber+3));
+          }
+        }
+      }
+      assertEquals(searchOp.getSearchEntries().size(), 4);
+
+
+    }
+    catch(Exception e)
+    {
+      fail("Ending test " + tn + " with exception:"
+          +  stackTraceToSingleLineString(e));      
+    }
+    debugInfo(tn, "Ending test with success");
+  }
+
+  private void ECLCompatReadFrom(int firstDraftChangeNumber)
+  {
+    String tn = "ECLCompatReadFrom/" + String.valueOf(firstDraftChangeNumber);
+    debugInfo(tn, "Starting test\n\n");
+
+    try
+    {
+      LDIFWriter ldifWriter = getLDIFWriter();
+
+      // Creates broker on o=test
+      ReplicationBroker server01 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING), (short) 1201, 
+          100, replicationServerPort,
+          1000, true);
+
+      String user1entryUUID = "11111111-1112-1113-1114-111111111115";
+
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      attributes.add("+");
+      attributes.add("*");
+
+      String filter = "(changenumber="+firstDraftChangeNumber+")";
+      debugInfo(tn, " Search: " + filter);
+      InternalSearchOperation searchOp = 
+        connection.processSearch(
+            ByteString.valueOf("cn=changelog"),
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 
+            0, // Size limit
+            0, // Time limit
+            false, // Types only
+            LDAPFilter.decode(filter),
+            attributes,
+            NO_CONTROL,
+            null);
+      sleep(500);
+
+      // test success
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+          searchOp.getErrorMessage().toString());
+
+      LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
+      assertEquals(searchOp.getSearchEntries().size(), 1);
+      if (entries != null)
+      {
+        int i=0;
+        for (SearchResultEntry resultEntry : entries)
+        {
+          i++;
+          debugInfo(tn, "Result entry returned:" + resultEntry.toLDIFString());
+          ldifWriter.writeEntry(resultEntry);
+          // check the entry has the right content
+          assertTrue(resultEntry.getDN().toNormalizedString().equalsIgnoreCase(
+              "cn=6,cn=changelog"));
+          checkValue(resultEntry,"replicationcsn",gblCN.toString());
+          checkValue(resultEntry,"replicaidentifier","1201");
+          checkValue(resultEntry,"changetype","add");
+          checkValue(resultEntry,"changelogcookie","o=test:"+gblCN.toString()+";o=test2:;");
+          checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+          checkValue(resultEntry,"changenumber","6");
+        }
+      }
+      server01.stop();
+    }
+    catch(Exception e)
+    {
+      fail("Ending test " + tn + " with exception:\n"
+          +  stackTraceToSingleLineString(e));      
+    }
+    debugInfo(tn, "Ending test with success");
+  }
+
+  /**
+   * Read the ECL in compat mode from firstDraftChangeNumber and to
+   * lastDraftChangeNumber.
+   * @param firstDraftChangeNumber
+   * @param lastDraftChangeNumber
+   */
+  private void ECLCompatReadFromTo(int firstDraftChangeNumber,
+      int lastDraftChangeNumber)
+  {
+    String tn = "ECLCompatReadFromTo/" +
+    String.valueOf(firstDraftChangeNumber) + "/" +
+    String.valueOf(lastDraftChangeNumber);
+
+    debugInfo(tn, "Starting test\n\n");
+
+    try
+    {
+      // search on 'cn=changelog'
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      attributes.add("+");
+      attributes.add("*");
+
+      String filter = "(&(changenumber>="+firstDraftChangeNumber+")(changenumber<="+lastDraftChangeNumber+"))";
+      debugInfo(tn, " Search: " + filter);
+      InternalSearchOperation searchOp = 
+        connection.processSearch(
+            ByteString.valueOf("cn=changelog"),
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 
+            0, // Size limit
+            0, // Time limit
+            false, // Types only
+            LDAPFilter.decode(filter),
+            attributes,
+            NO_CONTROL,
+            null);
+      sleep(500);
+
+      // test success
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+          searchOp.getErrorMessage().toString());      
+      assertEquals(searchOp.getSearchEntries().size(), 
+          lastDraftChangeNumber-firstDraftChangeNumber+1);
+    }
+    catch(Exception e)
+    {
+      fail("Ending test " + tn + " with exception:\n"
+          +  stackTraceToSingleLineString(e));      
+    }
+    debugInfo(tn, "Ending test with success");
+  }
+
+  /**
+   * Read the ECL in compat mode providing an unknown draft changenumber.
+   */
+  private void ECLCompatBadSeqnum()
+  {
+    String tn = "ECLCompatBadSeqnum";
+    debugInfo(tn, "Starting test\n\n");
+
+    try
+    {
+      // search on 'cn=changelog'
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      attributes.add("+");
+      attributes.add("*");
+
+      String filter = "(changenumber=1000)";
+      debugInfo(tn, " Search: " + filter);
+      InternalSearchOperation searchOp = 
+        connection.processSearch(
+            ByteString.valueOf("cn=changelog"),
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 
+            0, // Size limit
+            0, // Time limit
+            false, // Types only
+            LDAPFilter.decode(filter),
+            attributes,
+            NO_CONTROL,
+            null);
+      sleep(500);
+
+      // test success and no entries returned
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS, 
+          searchOp.getErrorMessage().toString());      
+      assertEquals(searchOp.getSearchEntries().size(), 0);
+    }
+    catch(Exception e)
+    {
+      fail("Ending test "+tn+" with exception:\n"
+          +  stackTraceToSingleLineString(e));      
+    }
+    debugInfo(tn, "Ending test with success");
+  }
+
+  /**
+   * Read the ECL in compat mode providing an unknown draft changenumber.
+   */
+  private void ECLFilterOnReplicationCsn()
+  {
+    String tn = "ECLFilterOnReplicationCsn";
+    debugInfo(tn, "Starting test\n\n");
+
+    try
+    {
+      LDIFWriter ldifWriter = getLDIFWriter();
+
+      // search on 'cn=changelog'
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      attributes.add("+");
+      attributes.add("*");
+
+      String filter = "(replicationcsn="+this.gblCN+")";
+      debugInfo(tn, " Search: " + filter);
+      InternalSearchOperation searchOp = 
+        connection.processSearch(
+            ByteString.valueOf("cn=changelog"),
+            SearchScope.WHOLE_SUBTREE,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 
+            0, // Size limit
+            0, // Time limit
+            false, // Types only
+            LDAPFilter.decode(filter),
+            attributes,
+            NO_CONTROL,
+            null);
+      sleep(500);
+
+      // test success and no entries returned
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS, 
+          searchOp.getErrorMessage().toString());      
+      assertEquals(searchOp.getSearchEntries().size(), 1);
+      
+      LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
+      assertEquals(searchOp.getSearchEntries().size(), 1);
+      if (entries != null)
+      {
+        int i=0;
+        for (SearchResultEntry resultEntry : entries)
+        {
+          i++;
+          debugInfo(tn, "Result entry returned:" + resultEntry.toLDIFString());
+          ldifWriter.writeEntry(resultEntry);
+          // check the DEL entry has the right content
+          checkValue(resultEntry,"replicationcsn",gblCN.toString());
+          // TODO:ECL check values of the other attributes
+        }
+      }
+
+    }
+    catch(Exception e)
+    {
+      fail("Ending test "+tn+" with exception:\n"
+          +  stackTraceToSingleLineString(e));      
+    }
+    debugInfo(tn, "Ending test with success");
+  }
+
+  /**
+   * Test that different values of filter are correctly decoded
+   * to find if the search op on the ECL can be optimized
+   * regarding the Draft changenumbers.
+   */
+  private void ECLFilterTest()
+  {
+    String tn = "ECLFilterTest";
+    debugInfo(tn, "Starting test\n\n");
+    try
+    {
+      StartECLSessionMsg startCLmsg = new StartECLSessionMsg();
+
+      //
+      ECLSearchOperation.evaluateFilter(startCLmsg,
+          SearchFilter.createFilterFromString("(objectclass=*)"));
+      assertEquals(startCLmsg.getFirstDraftChangeNumber(),-1);
+      assertEquals(startCLmsg.getLastDraftChangeNumber(),-1);
+
+      //
+      ECLSearchOperation.evaluateFilter(startCLmsg,
+          SearchFilter.createFilterFromString("(changenumber>=2)"));
+      assertEquals(startCLmsg.getFirstDraftChangeNumber(),2);
+      assertEquals(startCLmsg.getLastDraftChangeNumber(),-1);
+
+      //
+      ECLSearchOperation.evaluateFilter(startCLmsg,
+          SearchFilter.createFilterFromString("(&(changenumber>=2)(changenumber<=5))"));
+      assertEquals(startCLmsg.getFirstDraftChangeNumber(),2);
+      assertEquals(startCLmsg.getLastDraftChangeNumber(),5);
+
+      //
+      try
+      {
+        ECLSearchOperation.evaluateFilter(startCLmsg,
+            SearchFilter.createFilterFromString("(&(changenumber>=2)(changenumber<+5))"));
+        assertTrue((startCLmsg.getFirstDraftChangeNumber()==1));
+      }
+      catch(DirectoryException de)
+      {
+        assertTrue(de != null);
+      }
+
+      //
+      ECLSearchOperation.evaluateFilter(startCLmsg,
+          SearchFilter.createFilterFromString("(&(dc=x)(&(changenumber>=2)(changenumber<=5)))"));
+      assertEquals(startCLmsg.getFirstDraftChangeNumber(),2);
+      assertEquals(startCLmsg.getLastDraftChangeNumber(),5);
+
+      ECLSearchOperation.evaluateFilter(startCLmsg,
+          SearchFilter.createFilterFromString("(&(&(changenumber>=3)(changenumber<=4))(&(|(dc=y)(dc=x))(&(changenumber>=2)(changenumber<=5))))"));
+      assertEquals(startCLmsg.getFirstDraftChangeNumber(),3);
+      assertEquals(startCLmsg.getLastDraftChangeNumber(),4);
+
+      //
+      ECLSearchOperation.evaluateFilter(startCLmsg,
+          SearchFilter.createFilterFromString("(|(objectclass=*)(&(changenumber>=2)(changenumber<=5)))"));
+      assertEquals(startCLmsg.getFirstDraftChangeNumber(),-1);
+      assertEquals(startCLmsg.getLastDraftChangeNumber(),-1);
+
+      //
+      ECLSearchOperation.evaluateFilter(startCLmsg,
+          SearchFilter.createFilterFromString("(changenumber=8)"));
+      assertEquals(startCLmsg.getFirstDraftChangeNumber(),8);
+      assertEquals(startCLmsg.getLastDraftChangeNumber(),8);
+
+      //
+      ChangeNumberGenerator gen = new ChangeNumberGenerator((short) 1, 0);
+      ChangeNumber changeNumber1 = gen.newChangeNumber();      
+      ECLSearchOperation.evaluateFilter(startCLmsg,
+          SearchFilter.createFilterFromString("(replicationcsn="+changeNumber1+")"));
+      assertEquals(startCLmsg.getFirstDraftChangeNumber(),-1);
+      assertEquals(startCLmsg.getLastDraftChangeNumber(),-1);
+      assertEquals(startCLmsg.getChangeNumber(), changeNumber1);
+
+      
+    }
+    catch(Exception e)
+    {
+      fail("Ending "+tn+" test with exception:\n"
+          +  stackTraceToSingleLineString(e));
+    }
+    debugInfo(tn, "Ending test with success");
+  }
+
+  private void ECLCompatPurge()
+  {
+    String tn = "ECLCompatPurge";
+    debugInfo(tn, "Starting test\n\n");
+    try
+    {
+      DraftCNDbHandler draftdb = replicationServer.getDraftCNDbHandler();
+      assertEquals(draftdb.count(), 8);
+      draftdb.setPurgeDelay(1000);
+
+      // Now Purge the changelog db
+      this.replicationServer.clearDb();
+
+      // Expect changes purged from the changelog db to be sometimes
+      // also purged from the DraftCNDb.
+      while(draftdb.count()>0)
+      {
+        debugInfo(tn, "draftdb.count="+draftdb.count());
+        sleep(200);
+      }
+    }
+    catch(Exception e)
+    {
+      fail("Ending "+tn+" test with exception:\n"
+          +  stackTraceToSingleLineString(e));
+    }
+    debugInfo(tn, "Ending test with success");
+  }
+
+  private void ECLCompatTestLimits(int expectedFirst, int expectedLast)
+  {
+    String tn = "ECLCompatTestLimits";
+    debugInfo(tn, "Starting test\n\n");
+    try
+    {
+      LDIFWriter ldifWriter = getLDIFWriter();
+
+      // search on 'cn=changelog'
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      attributes.add("*");
+      attributes.add("+");
+
+      debugInfo(tn, " Search: rootDSE");
+      InternalSearchOperation searchOp = 
+        connection.processSearch(
+            ByteString.valueOf(""),
+            SearchScope.BASE_OBJECT,
+            DereferencePolicy.NEVER_DEREF_ALIASES, 
+            0, // Size limit
+            0, // Time limit
+            false, // Types only
+            LDAPFilter.decode("(objectclass=*)"),
+            attributes,
+            NO_CONTROL,
+            null);
+      sleep(500);
+
+      // test success and no entries returned
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS, 
+          searchOp.getErrorMessage().toString());      
+      assertEquals(searchOp.getSearchEntries().size(), 1);
+
+      LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
+      assertEquals(searchOp.getSearchEntries().size(), 1);
+      if (entries != null)
+      {
+        int i=0;
+        for (SearchResultEntry resultEntry : entries)
+        {
+          i++;
+          debugInfo(tn, "Result entry returned:" + resultEntry.toLDIFString());
+          ldifWriter.writeEntry(resultEntry);
+          checkValue(resultEntry,"firstchangenumber", 
+              String.valueOf(expectedFirst));
+          checkValue(resultEntry,"lastchangenumber", 
+              String.valueOf(expectedLast));
+        }
+      }
+    }
+    catch(Exception e)
+    {
+      fail("Ending "+tn+" test with exception:\n"
+          +  stackTraceToSingleLineString(e));
+    }
+    debugInfo(tn, "Ending test with success");
+  }
 }
\ No newline at end of file
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
index d10664a..d5bac70 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
@@ -221,7 +221,7 @@
 
     ReplicationBroker broker = new ReplicationBroker(null,
         state, baseDn.toNormalizedString(), serverId, window_size,
-        generationId, 100000, getReplSessionSecurity(), (byte)1);
+        generationId, 100000, getReplSessionSecurity(), (byte)1, 500);
     ArrayList<String> servers = new ArrayList<String>(1);
     servers.add("localhost:" + port);
     broker.start(servers);
@@ -381,7 +381,7 @@
   {
     ReplicationBroker broker = new ReplicationBroker(null,
         state, baseDn.toNormalizedString(), serverId, window_size, generationId,
-        100000, getReplSessionSecurity(), (byte)1);
+        100000, getReplSessionSecurity(), (byte)1, 500);
     ArrayList<String> servers = new ArrayList<String>(1);
     servers.add("localhost:" + port);
     broker.start(servers);
@@ -420,7 +420,7 @@
 
     ReplicationBroker broker = new ReplicationBroker(null,
         state, baseDn.toNormalizedString(), serverId, window_size,
-        generationId, 0, getReplSessionSecurity(), (byte)1);
+        generationId, 0, getReplSessionSecurity(), (byte)1, 500);
     ArrayList<String> servers = new ArrayList<String>(1);
     servers.add("localhost:" + port);
     broker.start(servers);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
index 73f378e..4f77b2c 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
@@ -210,7 +210,10 @@
       "ds-cfg-server-id: 1\n" + "ds-cfg-receive-status: true\n" +
       // heartbeat = 10 min so no need to emulate heartbeat in fake RS: session
       // not closed by client
-      "ds-cfg-heartbeat-interval: 600000ms\n";
+      "ds-cfg-heartbeat-interval: 600000ms\n" +
+      // heartbeat = 10 min so no need to emulate heartbeat in fake RS: session
+      // not closed by client
+     "ds-cfg-changetime-heartbeat-interval: 0ms\n";
 
     String configEntryLdif = null;
     switch (assuredMode)
@@ -253,7 +256,8 @@
       "ds-cfg-server-id: 1\n" + "ds-cfg-receive-status: true\n" +
       // heartbeat = 10 min so no need to emulate heartbeat in fake RS: session
       // not closed by client
-      "ds-cfg-heartbeat-interval: 600000ms\n";
+      "ds-cfg-heartbeat-interval: 600000ms\n" +
+      "ds-cfg-changetime-heartbeat-interval: 0ms\n";
 
     Entry domainCfgEntry = TestCaseUtils.entryFromLdifString(configEntryLdif);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
index a6c6d04..7f65980 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
@@ -186,6 +186,14 @@
   /**
    * {@inheritDoc}
    */
+  public long getChangetimeHeartbeatInterval()
+  {
+    return 0;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
   public long getMaxReceiveDelay()
   {
     return 0;
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java
index 6bc132c..d926b19 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java
@@ -685,7 +685,7 @@
     {
       super(serviceID, serverID);
       generationID = generationId;
-      startPublishService(replicationServers, window, heartbeatInterval);
+      startPublishService(replicationServers, window, heartbeatInterval, 500);
       startListenService();
     }
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/StateMachineTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/StateMachineTest.java
index b329306..e2436a5 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/StateMachineTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/StateMachineTest.java
@@ -324,7 +324,7 @@
   {
     ReplicationBroker broker = new ReplicationBroker(null,
       state, EXAMPLE_DN, dsId, 100, generationId, 0,
-      new ReplSessionSecurity(null, null, null, true), (byte) 1);
+      new ReplSessionSecurity(null, null, null, true), (byte) 1, 500);
     ArrayList<String> servers = new ArrayList<String>(1);
     servers.add("localhost:" + rs1Port);
     broker.start(servers);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
index ea14331..c73ac80 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
@@ -629,6 +629,7 @@
         (short) 123, (short) 45);
     op.setAttachment(SYNCHROCONTEXT, new DeleteContext(cn, "uniqueid"));
     DeleteMsg delmsg = new DeleteMsg(op);
+    int draftcn = 21;
 
     String serviceId = "serviceid";
 
@@ -639,9 +640,10 @@
           "o=test2:000001210b6f21e904b100000002 000001210b6f21e904b200000002;");
 
     // Constructor test
-    ECLUpdateMsg msg1 = new ECLUpdateMsg(delmsg, cookie, serviceId);
+    ECLUpdateMsg msg1 = new ECLUpdateMsg(delmsg, cookie, serviceId, draftcn);
     assertTrue(msg1.getCookie().equalsTo(cookie));
     assertTrue(msg1.getServiceId().equalsIgnoreCase(serviceId));
+    assertTrue((msg1.getDraftChangeNumber()==draftcn));
     DeleteMsg delmsg2 = (DeleteMsg)msg1.getUpdateMsg();
     assertTrue(delmsg.compareTo(delmsg2)==0);
 
@@ -651,6 +653,9 @@
     assertTrue(msg2.getCookie().equalsTo(cookie));
     assertTrue(msg2.getServiceId().equalsIgnoreCase(msg1.getServiceId()));
     assertTrue(msg2.getServiceId().equalsIgnoreCase(serviceId));
+    assertTrue(msg2.getDraftChangeNumber()==(msg1.getDraftChangeNumber()));
+    assertTrue(msg2.getDraftChangeNumber()==draftcn);
+    
     DeleteMsg delmsg1 = (DeleteMsg)msg1.getUpdateMsg();
     delmsg2 = (DeleteMsg)msg2.getUpdateMsg();
     assertTrue(delmsg2.compareTo(delmsg)==0);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/AssuredReplicationServerTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/AssuredReplicationServerTest.java
index 5eef7ee..db3179d 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/AssuredReplicationServerTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/AssuredReplicationServerTest.java
@@ -429,7 +429,7 @@
 
       List<String> replicationServers = new ArrayList<String>();
       replicationServers.add("localhost:" + rsPort);
-      fakeReplicationDomain.startPublishService(replicationServers, window, 1000);
+      fakeReplicationDomain.startPublishService(replicationServers, window, 1000, 500);
       if (startListen)
         fakeReplicationDomain.startListenService();
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/DraftCNDbHandlerTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/DraftCNDbHandlerTest.java
new file mode 100644
index 0000000..5dcfa62
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/DraftCNDbHandlerTest.java
@@ -0,0 +1,299 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+import java.net.ServerSocket;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.replication.ReplicationTestCase;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.ChangeNumberGenerator;
+import org.opends.server.replication.server.DraftCNDB.DraftCNDBCursor;
+import org.testng.annotations.Test;
+
+/**
+ * Test the DraftCNDbHandler class with 2 kinds of cleaning of the db :
+ * - periodic trim
+ * - call to clear method()
+ */
+public class DraftCNDbHandlerTest extends ReplicationTestCase
+{
+  /**
+   * This test makes basic operations of a DraftCNDb :
+   * - create the db
+   * - add records
+   * - read them with a cursor
+   * - set a very short trim period
+   * - wait for the db to be trimmed / here since the changes are not stored in
+   *   the replication changelog, the draftCNDb will be cleared.
+   *
+   * @throws Exception
+   */
+  @Test()
+  void testDraftCNDbHandlerTrim() throws Exception
+  {
+    File testRoot = null;
+    ReplicationServer replicationServer = null;
+    ReplicationDbEnv dbEnv = null;
+    DraftCNDbHandler handler = null;
+    try
+    {
+      TestCaseUtils.startServer();
+
+      //  find  a free port for the replicationServer
+      ServerSocket socket = TestCaseUtils.bindFreePort();
+      int changelogPort = socket.getLocalPort();
+      socket.close();
+
+      // configure a ReplicationServer.
+      ReplServerFakeConfiguration conf =
+        new ReplServerFakeConfiguration(changelogPort, null, 0,
+        2, 0, 100, null);
+      replicationServer = new ReplicationServer(conf);
+
+      // create or clean a directory for the DraftCNDbHandler
+      String buildRoot = System.getProperty(TestCaseUtils.PROPERTY_BUILD_ROOT);
+      String path = buildRoot + File.separator + "build" + File.separator +
+        "unit-tests" + File.separator + "DraftCNDbHandler";
+      testRoot = new File(path);
+      if (testRoot.exists())
+      {
+        TestCaseUtils.deleteDirectory(testRoot);
+      }
+      testRoot.mkdirs();
+
+      dbEnv = new ReplicationDbEnv(path, replicationServer);
+
+      handler = new DraftCNDbHandler(replicationServer, dbEnv);
+      handler.setPurgeDelay(0);
+
+      // Prepare data to be stored in the db
+      int sn1 = 3;
+      int sn2 = 4;
+      int sn3 = 5;
+      
+      String value1 = "value1";
+      String value2 = "value2";
+      String value3 = "value3";
+      
+      String serviceID1 = "serviceID1";
+      String serviceID2 = "serviceID2";
+      String serviceID3 = "serviceID3";
+
+      ChangeNumberGenerator gen = new ChangeNumberGenerator((short) 1, 0);
+      ChangeNumber changeNumber1 = gen.newChangeNumber();
+      ChangeNumber changeNumber2 = gen.newChangeNumber();
+      ChangeNumber changeNumber3 = gen.newChangeNumber();
+
+      // Add records
+      handler.add(sn1, value1, serviceID1, changeNumber1);
+      handler.add(sn2, value2, serviceID2, changeNumber2);
+      handler.add(sn3, value3, serviceID3, changeNumber3);
+
+      // The ChangeNumber should not get purged
+      int firstkey = handler.getFirstKey();
+      assertEquals(firstkey, sn1);
+      assertEquals(handler.getLastKey(), sn3);
+      
+      DraftCNDBCursor dbc = handler.getReadCursor(firstkey);
+      assertEquals(dbc.currentChangeNumber(), changeNumber1);
+      assertEquals(dbc.currentServiceID(), serviceID1);
+      assertEquals(dbc.currentValue(), value1);
+      assertTrue(dbc.toString().length() != 0);
+      
+      assertTrue(dbc.next());
+      
+      assertEquals(dbc.currentChangeNumber(), changeNumber2);
+      assertEquals(dbc.currentServiceID(), serviceID2);
+      assertEquals(dbc.currentValue(), value2);
+      
+      assertTrue(dbc.next());
+      
+      assertEquals(dbc.currentChangeNumber(), changeNumber3);
+      assertEquals(dbc.currentServiceID(), serviceID3);
+      assertEquals(dbc.currentValue(), value3);
+      
+      assertFalse(dbc.next());
+      
+      handler.releaseReadCursor(dbc);
+
+      handler.setPurgeDelay(100);
+
+      // Check the db is cleared.
+      while(handler.count()!=0)
+      {
+        Thread.sleep(200);
+      }
+      assertEquals(handler.getFirstKey(), 0);
+      assertEquals(handler.getLastKey(), 0);
+      
+      
+    } finally
+    {
+      if (handler != null)
+        handler.shutdown();
+      if (dbEnv != null)
+        dbEnv.shutdown();
+      if (replicationServer != null)
+      replicationServer.remove();
+      if (testRoot != null)
+        TestCaseUtils.deleteDirectory(testRoot);
+    }
+  }
+
+  /**
+   * This test makes basic operations of a DraftCNDb and explicitely call
+   * the clear() method instead of waiting for the periodic trim to clear
+   * it.
+   * - create the db
+   * - add records
+   * - read them with a cursor
+   * - clear the db.
+   * @throws Exception
+   */
+  @Test()
+  void testDraftCNDbHandlerClear() throws Exception
+  {
+    File testRoot = null;
+    ReplicationServer replicationServer = null;
+    ReplicationDbEnv dbEnv = null;
+    DraftCNDbHandler handler = null;
+    try
+    {
+      TestCaseUtils.startServer();
+
+      //  find  a free port for the replicationServer
+      ServerSocket socket = TestCaseUtils.bindFreePort();
+      int changelogPort = socket.getLocalPort();
+      socket.close();
+
+      // configure a ReplicationServer.
+      ReplServerFakeConfiguration conf =
+        new ReplServerFakeConfiguration(changelogPort, null, 0,
+        2, 0, 100, null);
+      replicationServer = new ReplicationServer(conf);
+
+      // create or clean a directory for the DraftCNDbHandler
+      String buildRoot = System.getProperty(TestCaseUtils.PROPERTY_BUILD_ROOT);
+      String path = buildRoot + File.separator + "build" + File.separator +
+        "unit-tests" + File.separator + "DraftCNDbHandler";
+      testRoot = new File(path);
+      if (testRoot.exists())
+      {
+        TestCaseUtils.deleteDirectory(testRoot);
+      }
+      testRoot.mkdirs();
+
+      dbEnv = new ReplicationDbEnv(path, replicationServer);
+
+      handler = new DraftCNDbHandler(replicationServer, dbEnv);
+      handler.setPurgeDelay(0);
+
+      //
+      assertTrue(handler.count()==0);
+      
+      // Prepare data to be stored in the db
+      int sn1 = 3;
+      int sn2 = 4;
+      int sn3 = 5;
+      
+      String value1 = "value1";
+      String value2 = "value2";
+      String value3 = "value3";
+      
+      String serviceID1 = "serviceID1";
+      String serviceID2 = "serviceID2";
+      String serviceID3 = "serviceID3";
+
+      ChangeNumberGenerator gen = new ChangeNumberGenerator((short) 1, 0);
+      ChangeNumber changeNumber1 = gen.newChangeNumber();
+      ChangeNumber changeNumber2 = gen.newChangeNumber();
+      ChangeNumber changeNumber3 = gen.newChangeNumber();
+
+      // Add records
+      handler.add(sn1, value1, serviceID1, changeNumber1);
+      handler.add(sn2, value2, serviceID2, changeNumber2);
+      handler.add(sn3, value3, serviceID3, changeNumber3);
+      Thread.sleep(500);
+
+      // Checks
+      assertEquals(handler.getFirstKey(), sn1);
+      assertEquals(handler.getLastKey(), sn3);
+      
+      assertEquals(handler.count(), 3, "Db count");
+
+      assertEquals(handler.getValue(sn1),value1);
+      assertEquals(handler.getValue(sn2),value2);
+      assertEquals(handler.getValue(sn3),value3);
+
+      DraftCNDbIterator it = handler.generateIterator(sn1);
+      assertEquals(it.getDraftCN(),sn1);
+      assertTrue(it.next());
+      assertEquals(it.getDraftCN(),sn2);
+      assertTrue(it.next());
+      assertEquals(it.getDraftCN(),sn3);
+      assertFalse(it.next());
+      it.releaseCursor();
+
+      it = handler.generateIterator(sn2);
+      assertEquals(it.getDraftCN(),sn2);
+      assertTrue(it.next());
+      assertEquals(it.getDraftCN(),sn3);
+      assertFalse(it.next());
+      it.releaseCursor();
+
+      it = handler.generateIterator(sn3);
+      assertEquals(it.getDraftCN(),sn3);
+      assertFalse(it.next());
+      it.releaseCursor();
+
+      // Clear ...
+      handler.clear();
+
+      // Check the db is cleared.
+      assertEquals(handler.getFirstKey(), 0);
+      assertEquals(handler.getLastKey(), 0);
+      assertTrue(handler.count()==0);
+
+    } finally
+    {
+      if (handler != null)
+        handler.shutdown();
+      if (dbEnv != null)
+        dbEnv.shutdown();
+      if (replicationServer != null)
+        replicationServer.remove();
+      if (testRoot != null)
+        TestCaseUtils.deleteDirectory(testRoot);
+    }
+  }
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeReplicationDomain.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeReplicationDomain.java
index a274a90..fbb952e 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeReplicationDomain.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeReplicationDomain.java
@@ -67,7 +67,7 @@
       BlockingQueue<UpdateMsg> queue) throws ConfigException
   {
     super(serviceID, serverID);
-    startPublishService(replicationServers, window, heartbeatInterval);
+    startPublishService(replicationServers, window, heartbeatInterval, 500);
     startListenService();
     this.queue = queue;
   }
@@ -83,7 +83,7 @@
       int exportedEntryCount) throws ConfigException
   {
     super(serviceID, serverID);
-    startPublishService(replicationServers, window, heartbeatInterval);
+    startPublishService(replicationServers, window, heartbeatInterval, 500);
     startListenService();
     this.exportString = exportString;
     this.importString = importString;
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeStressReplicationDomain.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeStressReplicationDomain.java
index 86f6263..d327a00 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeStressReplicationDomain.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeStressReplicationDomain.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2008 Sun Microsystems, Inc.
+ *      Copyright 2008-2009 Sun Microsystems, Inc.
  */
 package org.opends.server.replication.service;
 
@@ -63,7 +63,7 @@
       BlockingQueue<UpdateMsg> queue) throws ConfigException
   {
     super(serviceID, serverID);
-    startPublishService(replicationServers, window, heartbeatInterval);
+    startPublishService(replicationServers, window, heartbeatInterval, 500);
     startListenService();
     this.queue = queue;
   }
@@ -78,7 +78,7 @@
       StringBuilder importString) throws ConfigException
   {
     super(serviceID, serverID);
-    startPublishService(replicationServers, window, heartbeatInterval);
+    startPublishService(replicationServers, window, heartbeatInterval, 500);
     startListenService();
     this.exportString = exportString;
     this.importString = importString;

--
Gitblit v1.10.0