From ccc4127f23f63214f4dc2f94d26a021a3ec2eec6 Mon Sep 17 00:00:00 2001
From: pgamba <pgamba@localhost>
Date: Wed, 10 Jun 2009 08:43:50 +0000
Subject: [PATCH] External Changelog - first step - related issues 495,  519

---
 opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java                           |  215 
 opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java                                               |   11 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java             |   60 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java |  160 
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java                             |   12 
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartECLMsg.java                              |  377 +
 opendj-sdk/opends/src/messages/messages/protocol.properties                                                             |    3 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java                                    | 3432 ++++----------
 opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java                           |   19 
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java                                 |   12 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java                          |  542 +
 opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java                         |    2 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java                                     |  109 
 opendj-sdk/opends/src/server/org/opends/server/replication/common/ExternalChangeLogSession.java                         |   52 
 opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java                                      |    9 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java                         |  791 +++
 opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java                         |    4 
 opendj-sdk/opends/src/server/org/opends/server/controls/EntryChangelogNotificationControl.java                          |  173 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java                                |  200 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.java                                     |   48 
 opendj-sdk/opends/src/messages/messages/replication.properties                                                          |    9 
 opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/package-info.java                      |   38 
 opendj-sdk/opends/resource/config/config.ldif                                                                           |    1 
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java                                   |  189 
 opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java                                                |   20 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java                                   |    5 
 opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java                               |  309 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/common/ExternalChangeLogTest.java    | 1569 ++++++
 opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java                | 1300 +++++
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java                                 |   12 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ExternalChangelogControlTest.java       |  100 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/DataServerHandler.java                                |  724 +++
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java                             |  432 +
 opendj-sdk/opends/src/server/org/opends/server/replication/server/MessageHandler.java                                   |  912 +++
 opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java                |  220 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java                     |   85 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java    |   27 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java           |    6 
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java                               |    7 
 opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java                                           |    2 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java                                 | 1551 ++++++
 opendj-sdk/opends/src/server/org/opends/server/controls/ExternalChangelogRequestControl.java                            |  170 
 opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java                                  |  306 +
 43 files changed, 11,479 insertions(+), 2,746 deletions(-)

diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index a6d685c..05f9afe 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -78,6 +78,7 @@
 ds-cfg-global-aci: (target="ldap:///")(targetscope="base")(targetattr="objectClass||namingContexts||supportedAuthPasswordSchemes||supportedControl||supportedExtension||supportedFeatures||supportedLDAPVersion||supportedSASLMechanisms||vendorName||vendorVersion")(version 3.0; acl "User-Visible Root DSE Operational Attributes"; allow (read,search,compare) userdn="ldap:///anyone";)
 ds-cfg-global-aci: (targetattr="createTimestamp||creatorsName||modifiersName||modifyTimestamp||entryDN||entryUUID||subschemaSubentry")(version 3.0; acl "User-Visible Operational Attributes"; allow (read,search,compare) userdn="ldap:///anyone";)
 ds-cfg-global-aci: (target="ldap:///dc=replicationchanges")(targetattr="*")(version 3.0; acl "Replication backend access"; deny (all) userdn="ldap:///anyone";)
+ds-cfg-global-aci: (target="ldap:///cn=changelog")(targetattr="*")(version 3.0; acl "External changelog access"; deny (all) userdn="ldap:///anyone";)
 cn: Access Control Handler
 ds-cfg-java-class: org.opends.server.authorization.dseecompat.AciHandler
 ds-cfg-enabled: true
diff --git a/opendj-sdk/opends/src/messages/messages/protocol.properties b/opendj-sdk/opends/src/messages/messages/protocol.properties
index ed569e5..337198c 100644
--- a/opendj-sdk/opends/src/messages/messages/protocol.properties
+++ b/opendj-sdk/opends/src/messages/messages/protocol.properties
@@ -1411,4 +1411,7 @@
 SEVERE_ERR_SNMP_CONNHANDLER_OPENDMK_JARFILES_NOT_OPERATIONAL_1507=The required \
  classes could not be loaded using jar file '%s'.  Verify that the jar file \
  is not corrupted
+MILD_ERR_CANNOT_DECODE_CONTROL_VALUE_1508=Cannot decode the provided \
+ control %s because an error occurred while attempting to \
+ decode the control value:  %s
 
diff --git a/opendj-sdk/opends/src/messages/messages/replication.properties b/opendj-sdk/opends/src/messages/messages/replication.properties
index 4d27c47..b77bb30 100644
--- a/opendj-sdk/opends/src/messages/messages/replication.properties
+++ b/opendj-sdk/opends/src/messages/messages/replication.properties
@@ -365,4 +365,11 @@
 SEVERE_ERR_COULD_NOT_START_REPLICATION_154=The Replication was not started \
  on base-dn %s : %s
 MILD_ERR_ERROR_RETRIEVING_MONITOR_DATA_155=Error retrieving monitoring data: %s
-SEVERE_ERR_BYTE_COUNT_156=The Server Handler byte count is not correct (Fixed)
\ No newline at end of file
+SEVERE_ERR_EXCEPTION_LOCKING_RS_DOMAIN_156=Caught exception when locking \
+ the replication server domain: %s 
+SEVERE_ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE_157=Replication \
+ protocol error. Bad message type. %s received, %s required  
+SEVERE_ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED_158=The provided cookie is \
+ valid in the current context due to %s. Full resync is required
+SEVERE_ERR_BYTE_COUNT_159=The Server Handler byte count is not correct (Fixed)
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/controls/EntryChangelogNotificationControl.java b/opendj-sdk/opends/src/server/org/opends/server/controls/EntryChangelogNotificationControl.java
new file mode 100644
index 0000000..402e4d1
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/controls/EntryChangelogNotificationControl.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.controls;
+import static org.opends.messages.ProtocolMessages.ERR_ECN_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_ECN_NO_CONTROL_VALUE;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.protocols.asn1.ASN1Constants.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1;
+import org.opends.server.protocols.asn1.ASN1Reader;
+import org.opends.server.protocols.asn1.ASN1Writer;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.Control;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+
+
+
+/**
+ * This class implements the ECL cookie control.
+ * It may be included in entries returned in response to a search or
+ * persistent search operation.
+ */
+public class EntryChangelogNotificationControl
+       extends Control
+       {
+  // The tracer object for the debug logger.
+  private static final DebugTracer TRACER = getTracer();
+
+  // The cookie value - payload of this control.
+  private String cookie;
+
+  /**
+   * ControlDecoder implentation to decode this control from a ByteString.
+   */
+  private final static class Decoder
+  implements ControlDecoder<EntryChangelogNotificationControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public EntryChangelogNotificationControl decode(
+        boolean isCritical, ByteString value) throws DirectoryException
+        {
+      if (value == null)
+      {
+        // FIXME:ECL Create error messages dedicated to this control
+        Message message = ERR_ECN_NO_CONTROL_VALUE.get();
+        throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
+      }
+
+
+      String cookie = null;
+      ASN1Reader reader = ASN1.getReader(value);
+      try
+      {
+        reader.readStartSequence();
+        cookie = reader.readOctetStringAsString();
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        Message message =
+          ERR_ECN_CANNOT_DECODE_VALUE.get(getExceptionMessage(e));
+        throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message, e);
+      }
+      return new EntryChangelogNotificationControl(isCritical, cookie);
+        }
+
+    public String getOID()
+    {
+      return OID_ECL_COOKIE_EXCHANGE_CONTROL;
+    }
+
+  }
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<EntryChangelogNotificationControl>
+  DECODER = new Decoder();
+
+  /**
+   * Creates a new entry change notification control with the provided
+   * information.
+   *
+   * @param  isCritical  Indicates whether this control should be
+   *                     considered critical in processing the
+   *                     request.
+   * @param  cookie      The provided cookie value.
+   */
+  public EntryChangelogNotificationControl(boolean isCritical,
+      String cookie)
+  {
+    super(OID_ECL_COOKIE_EXCHANGE_CONTROL, isCritical);
+    this.cookie = cookie;
+  }
+
+  /**
+   * Writes this control's value to an ASN.1 writer. The value (if any)
+   * must be written as an ASN1OctetString.
+   *
+   * @param writer The ASN.1 output stream to write to.
+   * @throws IOException If a problem occurs while writing to the stream.
+   */
+  public void writeValue(ASN1Writer writer) throws IOException {
+    writer.writeStartSequence(UNIVERSAL_OCTET_STRING_TYPE);
+    writer.writeOctetString(cookie.toString());
+    writer.writeEndSequence();
+  }
+
+
+
+  /**
+   * Retrieves the change type for this entry change notification control.
+   *
+   * @return  The change type for this entry change notification control.
+   */
+  public String getCookie()
+  {
+    return cookie;
+  }
+
+  /**
+   * Appends a string representation of this entry change notification control
+   * to the provided buffer.
+   *
+   * @param  buffer  The buffer to which the information should be appended.
+   */
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("EntryChangelogNotificationControl(cookie=");
+    buffer.append(cookie.toString());
+    buffer.append(")");
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/controls/ExternalChangelogRequestControl.java b/opendj-sdk/opends/src/server/org/opends/server/controls/ExternalChangelogRequestControl.java
new file mode 100644
index 0000000..8747c4f
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/controls/ExternalChangelogRequestControl.java
@@ -0,0 +1,170 @@
+/*
+ * 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.controls;
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.server.protocols.asn1.ASN1Constants.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1;
+import org.opends.server.protocols.asn1.ASN1Reader;
+import org.opends.server.protocols.asn1.ASN1Writer;
+import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.Control;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+
+
+
+
+/**
+ * This class implements the persistent search control defined in
+ * draft-ietf-ldapext-psearch.  It makes it possible for clients to be notified
+ * of changes to information in the Directory Server as they occur.
+ */
+public class ExternalChangelogRequestControl
+       extends Control
+{
+  private static final DebugTracer TRACER = getTracer();
+
+  private MultiDomainServerState cookie;
+
+  /**
+   * ControlDecoder implentation to decode this control from a ByteString.
+   */
+  private final static class Decoder
+      implements ControlDecoder<ExternalChangelogRequestControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public ExternalChangelogRequestControl decode(boolean isCritical,
+        ByteString value)
+    throws DirectoryException
+    {
+      if (value == null)
+      {
+        // FIXME:ECL In the request cookie, empty value is currently rejected.
+        Message message = ERR_PSEARCH_NO_CONTROL_VALUE.get();
+        throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      MultiDomainServerState mdss;
+      String mdssValue = null;
+      try
+      {
+        mdssValue = reader.readOctetStringAsString();
+        mdss = new MultiDomainServerState(mdssValue);
+      }
+      catch (Exception e)
+      {
+        try
+        {
+          mdssValue = value.toString();
+          mdss = new MultiDomainServerState(mdssValue);
+        }
+        catch (Exception e2)
+        {
+          Message message =
+            ERR_CANNOT_DECODE_CONTROL_VALUE.get(
+                getOID() + " x=" + value.toHex() + " v="
+                + mdssValue , getExceptionMessage(e).toString());
+          throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message, e);
+        }
+      }
+      return new ExternalChangelogRequestControl(isCritical, mdss);
+    }
+
+    public String getOID()
+    {
+      return OID_ECL_COOKIE_EXCHANGE_CONTROL;
+    }
+
+  }
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<ExternalChangelogRequestControl> DECODER =
+    new Decoder();
+
+  /**
+   * Create a new external change log request control to contain the cookie.
+   * @param isCritical Specifies whether the control is critical.
+   * @param cookie Specifies the cookie value.
+   */
+  public ExternalChangelogRequestControl(boolean isCritical,
+      MultiDomainServerState cookie)
+  {
+    super(OID_ECL_COOKIE_EXCHANGE_CONTROL, isCritical);
+    this.cookie = cookie;
+  }
+
+  /**
+   * Returns the cookie value.
+   * @return The cookie value.
+   */
+  public MultiDomainServerState getCookie()
+  {
+    return this.cookie;
+  }
+
+  /**
+   * Dump a string representation of this object to the provided bufer.
+   * @param buffer The provided buffer.
+   */
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("ExternalChangelogRequestControl(cookie=");
+    this.cookie.toString(buffer);
+    buffer.append(")");
+  }
+
+  /**
+   * Writes this control's value to an ASN.1 writer. The value
+   * (if any) must be written as an ASN1OctetString.
+   *
+   * @param writer The ASN.1 writer to use.
+   * @throws IOException If a problem occurs while writing to the
+   *                     stream.
+   */
+  protected void writeValue(ASN1Writer writer)
+      throws IOException
+  {
+    writer.writeStartSequence(UNIVERSAL_OCTET_STRING_TYPE);
+    writer.writeOctetString(this.cookie.toString());
+    writer.writeEndSequence();
+  }
+}
+
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 5453b3c..26feafc 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
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Copyright 2006-2009 Sun Microsystems, Inc.
  */
 package org.opends.server.core;
 
@@ -240,6 +240,15 @@
   }
 
 
+  /**
+   * Get the search operation associated with this persistent search.
+   *
+   * @return The search operation associated with this persistent search.
+   */
+  public SearchOperation getSearchOperation()
+  {
+    return searchOperation;
+  }
 
   /**
    * Notifies the persistent searches that an entry has been added.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java b/opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java
index 37455d6..ecc472a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java
@@ -453,7 +453,7 @@
    */
   public static boolean debugEnabled()
   {
-    return enabled;
+    return true;
   }
 
   /**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/common/ExternalChangeLogSession.java b/opendj-sdk/opends/src/server/org/opends/server/replication/common/ExternalChangeLogSession.java
new file mode 100644
index 0000000..927537b
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/common/ExternalChangeLogSession.java
@@ -0,0 +1,52 @@
+/*
+ * 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 org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.types.DirectoryException;
+
+/**
+ * This interface defines a session used to search the external changelog
+ * in the Directory Server.
+ */
+public interface ExternalChangeLogSession
+{
+  /**
+   * Returns the next message available for the ECL (blocking).
+   * @return the next available message from the ECL.
+   * @throws DirectoryException When an error occurs.
+   */
+  public abstract ECLUpdateMsg getNextUpdate()
+  throws DirectoryException;
+
+  /**
+   * Closes the session.
+   * @throws DirectoryException when needed.
+   */
+  public abstract void close()
+  throws DirectoryException;
+}
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
new file mode 100644
index 0000000..7a63049
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java
@@ -0,0 +1,215 @@
+/*
+ * 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.common;
+
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+import java.util.Iterator;
+import java.util.TreeMap;
+
+import org.opends.server.loggers.debug.DebugTracer;
+
+
+/**
+ * This object is used to store a list of ServerState object, one by
+ * replication domain. Globally, it is the generalization of ServerState
+ * (that applies to one domain) to a list of domains.
+ */
+public class MultiDomainServerState implements Iterable<String>
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  /**
+   * The list of (domain service id, ServerState).
+   */
+  private TreeMap<String, ServerState> list;
+
+  /**
+   * Creates a new empty object.
+   */
+  public MultiDomainServerState()
+  {
+    list = new TreeMap<String, ServerState>();
+  }
+
+  /**
+   * Empty the object..
+   * After this call the object will be in the same state as if it
+   * was just created.
+   */
+  public void clear()
+  {
+    synchronized (this)
+    {
+      list.clear();
+    }
+  }
+
+  /**
+   * Update the ServerState of the provided serviceId with the
+   * replication change number provided.
+   *
+   * @param serviceId    The provided serviceId.
+   * @param changeNumber The provided ChangeNumber.
+   *
+   * @return a boolean indicating if the update was meaningful.
+   */
+  public boolean update(String serviceId, ChangeNumber changeNumber)
+  {
+    if (changeNumber == null)
+      return false;
+
+    synchronized(this)
+    {
+      Short serverId =  changeNumber.getServerId();
+      ServerState oldServerState = list.get(serviceId);
+      if (oldServerState == null)
+        oldServerState = new ServerState();
+
+      if (changeNumber.newer(oldServerState.getMaxChangeNumber(serverId)))
+      {
+        oldServerState.update(changeNumber);
+        list.put(serviceId,oldServerState);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+  /**
+   * Create an object from a string representation.
+   * @param mdss The provided string representation of the state.
+   */
+  public MultiDomainServerState(String mdss)
+  {
+    list = new TreeMap<String, ServerState>();
+    if ((mdss != null) &&
+        (mdss.length()>0))
+    {
+      String[] domains = mdss.split(";");
+      for (String domain : domains)
+      {
+        String[] fields = domain.split(":");
+        String name = fields[0];
+        ServerState ss = new ServerState();
+        if (fields.length>1)
+        {
+          String strState = fields[1].trim();
+          String[] strCN = strState.split(" ");
+          for (String sr : strCN)
+          {
+            ChangeNumber fromChangeNumber = new ChangeNumber(sr);
+            ss.update(fromChangeNumber);
+          }
+        }
+        this.list.put(name, ss);
+      }
+    }
+  }
+
+  /**
+   * Returns a string representation of this object.
+   * @return The string representation.
+   */
+  public String toString()
+  {
+    String res = "";
+    if ((list != null) && (!list.isEmpty()))
+    {
+      for (String serviceId  : list.keySet())
+      {
+        ServerState ss = list.get(serviceId);
+        res += serviceId + ":" + ss.toString();
+        res += ";";
+      }
+    }
+    return res;
+  }
+
+  /**
+   * Dump a string representation in the provided buffer.
+   * @param buffer The provided buffer.
+   */
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append(this.toString());
+  }
+
+
+  /**
+   * Tests if the state is empty.
+   *
+   * @return True if the state is empty.
+   */
+  public boolean isEmpty()
+  {
+    return list.isEmpty();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterator<String> iterator()
+  {
+    return list.keySet().iterator();
+  }
+
+  /**
+   * Test if this object equals the provided other object.
+   * @param other The other object with which we want to test equality.
+   * @return      Returns True if this equals other, else return false.
+   */
+  public boolean equalsTo(MultiDomainServerState other)
+  {
+    return ((this.cover(other)) && (other.cover(this)));
+  }
+
+  /**
+   * Test if this object covers the provided covered object.
+   * @param  covered The provided object.
+   * @return true when this covers the provided object.
+   */
+  public boolean cover(MultiDomainServerState covered)
+  {
+    for (String serviceId : covered.list.keySet())
+    {
+      ServerState state = list.get(serviceId);
+      ServerState coveredState = covered.list.get(serviceId);
+      if ((coveredState == null) || (!state.cover(coveredState)))
+      {
+        return false;
+      }
+    }
+    return true;
+  }
+}
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 0eac1cf..bf7565b 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
@@ -122,6 +122,8 @@
 
   /**
    * Get the length of the next String encoded in the in byte array.
+   * This method is used to cut the different parts (server ids, change number)
+   * of a server state.
    *
    * @param in the byte array where to calculate the string.
    * @param pos the position whre to start from in the byte array.
@@ -135,7 +137,7 @@
     while (in[offset++] != 0)
     {
       if (offset >= in.length)
-        throw new DataFormatException("byte[] is not a valid modify msg");
+        throw new DataFormatException("byte[] is not a valid server state");
       length++;
     }
     return length;
@@ -251,7 +253,6 @@
    * Return the text representation of ServerState.
    * @return the text representation of ServerState
    */
-  @Override
   public String toString()
   {
     StringBuilder buffer = new StringBuilder();
@@ -261,9 +262,11 @@
       for (Short key  : list.keySet())
       {
         ChangeNumber change = list.get(key);
+        buffer.append(change.toString());
         buffer.append(" ");
-        buffer.append(change.toStringUI());
       }
+      if (!list.isEmpty())
+        buffer.deleteCharAt(buffer.length()-1);
     }
 
     return buffer.toString();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
index 8110a81..68031e7 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
@@ -817,4 +817,23 @@
   {
     return domains.size();
   }
+
+  /**
+   * Gets the baseDn of the domains that have a private backend.
+   * @return The private baseDN.
+   */
+  public static ArrayList<String> getPrivateDomains()
+  {
+    ArrayList<String> privateDNs = new ArrayList<String>();
+
+    for (LDAPReplicationDomain domain : domains.values())
+    {
+      Backend b = domain.getBackend();
+      if (b != null)
+        if (b.isPrivateBackend())
+          privateDNs.add(domain.getBaseDN().toNormalizedString());
+    }
+    return privateDNs;
+  }
+
 }
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
new file mode 100644
index 0000000..0e8e8b5
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java
@@ -0,0 +1,189 @@
+/*
+ * 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.protocol;
+
+import java.io.UnsupportedEncodingException;
+import java.util.zip.DataFormatException;
+import org.opends.server.replication.common.MultiDomainServerState;
+
+/**
+ * Container for the ECL information sent from the ReplicationServer
+ * to the client part (either broker over the protocol, or ECLSession).
+ */
+public class ECLUpdateMsg extends ReplicationMsg
+{
+  // The replication change returned.
+  private final LDAPUpdateMsg updateMsg;
+
+  // The serviceId (baseDN) of the domain to which applies the change.
+  private final String serviceId;
+
+  // The value of the cookie updated with the current change
+  private MultiDomainServerState cookie;
+
+  /**
+   * Creates a new message.
+   * @param update    The provided update.
+   * @param cookie    The provided cookie value
+   * @param serviceId The provided serviceId.
+   */
+  public ECLUpdateMsg(LDAPUpdateMsg update, MultiDomainServerState cookie,
+      String serviceId)
+  {
+    this.cookie = cookie;
+    this.serviceId = serviceId;
+    this.updateMsg = update;
+  }
+
+  /**
+   * Creates a new message from its encoded form.
+   *
+   * @param in The byte array containing the encoded form of the message.
+   * @throws DataFormatException If the byte array does not contain
+   *         a valid encoded form of the message.
+   * @throws UnsupportedEncodingException when it occurs.
+   * @throws NotSupportedOldVersionPDUException when it occurs.
+   */
+  public ECLUpdateMsg(byte[] in)
+   throws DataFormatException,
+          UnsupportedEncodingException,
+          NotSupportedOldVersionPDUException
+  {
+    try
+    {
+      if (in[0] != MSG_TYPE_ECL_UPDATE)
+      {
+        throw new DataFormatException("byte[] is not a valid " +
+            this.getClass().getCanonicalName());
+      }
+      int pos = 1;
+
+      // Decode the cookie
+      int length = getNextLength(in, pos);
+      String cookieStr = new String(in, pos, length, "UTF-8");
+      this.cookie = new MultiDomainServerState(cookieStr);
+      pos += length + 1;
+
+      // Decode the serviceId
+      length = getNextLength(in, pos);
+      this.serviceId = 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;
+      byte[] encodedMsg = new byte[length];
+      System.arraycopy(in, pos, encodedMsg, 0, length);
+      ReplicationMsg rmsg = ReplicationMsg.generateMsg(encodedMsg);
+      this.updateMsg = (LDAPUpdateMsg)rmsg;
+    }
+    catch (UnsupportedEncodingException e)
+    {
+      throw new DataFormatException("UTF-8 is not supported by this jvm.");
+    }
+  }
+
+  /**
+   * Getter for the cookie value.
+   * @return The cookie value.
+   */
+  public MultiDomainServerState getCookie()
+  {
+    return cookie;
+  }
+
+  /**
+   * Setter for the cookie value.
+   * @param cookie The provided cookie value.
+   */
+  public void setCookie(MultiDomainServerState cookie)
+  {
+    this.cookie = cookie;
+  }
+
+  /**
+   * Getter for the serviceId.
+   * @return The serviceId.
+   */
+  public  String getServiceId()
+  {
+    return serviceId;
+  }
+
+  /**
+   * Getter for the message.
+   * @return The included replication message.
+   */
+  public  UpdateMsg getUpdateMsg()
+  {
+    return updateMsg;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return "ECLUpdateMsg:[" +
+    "updateMsg: " + updateMsg +
+    "cookie: " + cookie +
+    "serviceId: " + serviceId + "]";
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public byte[] getBytes() throws UnsupportedEncodingException
+  {
+    byte[] byteCookie    = String.valueOf(cookie).getBytes("UTF-8");
+    byte[] byteServiceId = String.valueOf(serviceId).getBytes("UTF-8");
+    byte[] byteUpdateMsg = updateMsg.getBytes();
+
+    int length = 1 + byteCookie.length +
+                 1 + byteServiceId.length +
+                 1 + byteUpdateMsg.length + 1;
+
+    byte[] resultByteArray = new byte[length];
+
+    /* Encode type */
+    resultByteArray[0] = MSG_TYPE_ECL_UPDATE;
+    int pos = 1;
+
+    // Encode cookie
+    pos = addByteArray(byteCookie, resultByteArray, pos);
+
+    // Encode serviceid
+    pos = addByteArray(byteServiceId, resultByteArray, pos);
+
+    // Encode msg
+    pos = addByteArray(byteUpdateMsg, resultByteArray, pos);
+
+    return resultByteArray;
+  }
+}
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 865d97c..96b0f6a 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
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2007-2008 Sun Microsystems, Inc.
+ *      Copyright 2007-2009 Sun Microsystems, Inc.
  */
 
 package org.opends.server.replication.protocol;
@@ -103,9 +103,8 @@
     boolean gotOneFailure = false;
     if (debugEnabled())
     {
-      TRACER.debugInfo("Heartbeat monitor is starting, expected interval is " +
-                heartbeatInterval +
-          stackTraceToSingleLineString(new Exception()));
+      TRACER.debugInfo(this + " is starting, expected interval is " +
+                heartbeatInterval);
     }
     try
     {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java
index f1bdb60..0201146 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.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.protocol;
 
@@ -167,9 +167,13 @@
         pos += length + 1;
       }
 
-      /*
-       * read the ServerState
-       */
+      // Read the ServerState
+      // Caution: ServerState MUST be the last field. Because ServerState can
+      // contain null character (string termination of sererid string ..) it
+      // cannot be decoded using getNextLength() like the other fields. The
+      // only way is to rely on the end of the input buffer : and that forces
+      // the ServerState to be the last. This should be changed and we want to
+      // have more than one ServerState field.
       serverState = new ServerState(in, pos, in.length - 1);
     } catch (UnsupportedEncodingException e)
     {
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 fdce792..3f70856 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,6 +70,9 @@
   static final byte MSG_TYPE_START_SESSION = 27;
   static final byte MSG_TYPE_CHANGE_STATUS = 28;
   static final byte MSG_TYPE_GENERIC_UPDATE = 29;
+  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;
 
   // Adding a new type of message here probably requires to
   // change accordingly generateMsg method below
@@ -215,6 +218,15 @@
       case MSG_TYPE_GENERIC_UPDATE:
         msg = new UpdateMsg(buffer);
       break;
+      case MSG_TYPE_START_ECL:
+        msg = new ServerStartECLMsg(buffer);
+      break;
+      case MSG_TYPE_START_ECL_SESSION:
+        msg = new StartECLSessionMsg(buffer);
+      break;
+      case MSG_TYPE_ECL_UPDATE:
+        msg = new ECLUpdateMsg(buffer);
+      break;
       default:
         throw new DataFormatException("received message with unknown type");
     }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartECLMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartECLMsg.java
new file mode 100644
index 0000000..366ff2d
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartECLMsg.java
@@ -0,0 +1,377 @@
+/*
+ * 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.protocol;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.zip.DataFormatException;
+
+import org.opends.server.replication.common.ServerState;
+
+/**
+ * This message is used by LDAP server when they first connect.
+ * to a replication server to let them know who they are and what is their state
+ * (their RUV)
+ */
+public class ServerStartECLMsg extends StartMsg
+{
+  private String serverURL;
+  private int maxReceiveQueue;
+  private int maxSendQueue;
+  private int maxReceiveDelay;
+  private int maxSendDelay;
+  private int windowSize;
+  private ServerState serverState = null;
+
+  /**
+   * The time in milliseconds between heartbeats from the replication
+   * server.  Zero means heartbeats are off.
+   */
+  private long heartbeatInterval = 0;
+
+  /**
+   * Whether to continue using SSL to encrypt messages after the start
+   * messages have been exchanged.
+   */
+
+  private boolean sslEncryption;
+
+  /**
+   * Creates a new ServerStartMsg. This message is to be sent by an LDAP
+   * Server after being connected to a replication server for a given
+   * replication domain.
+   *
+   * @param baseDn   The base DN.
+   * @param maxReceiveDelay The max receive delay for this server.
+   * @param maxReceiveQueue The max receive Queue for this server.
+   * @param maxSendDelay The max Send Delay from this server.
+   * @param maxSendQueue The max send Queue from this server.
+   * @param windowSize   The window size used by this server.
+   * @param heartbeatInterval The requested heartbeat interval.
+   * @param serverState  The state of this server.
+   * @param protocolVersion The replication protocol version of the creator.
+   * @param generationId The generationId for this server.
+   * @param sslEncryption Whether to continue using SSL to encrypt messages
+   *                      after the start messages have been exchanged.
+   * @param groupId The group id of the DS for this DN
+   */
+  public ServerStartECLMsg(String baseDn, int maxReceiveDelay,
+                            int maxReceiveQueue, int maxSendDelay,
+                            int maxSendQueue, int windowSize,
+                            long heartbeatInterval,
+                            ServerState serverState,
+                            short protocolVersion,
+                            long generationId,
+                            boolean sslEncryption,
+                            byte groupId)
+  {
+    super(protocolVersion, generationId);
+
+    this.maxReceiveDelay = maxReceiveDelay;
+    this.maxReceiveQueue = maxReceiveQueue;
+    this.maxSendDelay = maxSendDelay;
+    this.maxSendQueue = maxSendQueue;
+    this.windowSize = windowSize;
+    this.heartbeatInterval = heartbeatInterval;
+    this.sslEncryption = sslEncryption;
+    this.serverState = serverState;
+    this.groupId = groupId;
+
+    try
+    {
+      /* TODO : find a better way to get the server URL */
+      this.serverURL = InetAddress.getLocalHost().getHostName();
+    } catch (UnknownHostException e)
+    {
+      this.serverURL = "Unknown host";
+    }
+  }
+
+  /**
+   * Creates a new ServerStartMsg from its encoded form.
+   *
+   * @param in The byte array containing the encoded form of the
+   *           ServerStartMsg.
+   * @throws DataFormatException If the byte array does not contain a valid
+   *                             encoded form of the ServerStartMsg.
+   */
+  public ServerStartECLMsg(byte[] in) throws DataFormatException
+  {
+    byte[] allowedPduTypes = new byte[1];
+    allowedPduTypes[0] = MSG_TYPE_START_ECL;
+    headerLength = decodeHeader(allowedPduTypes, in);
+
+    try
+    {
+      /* first bytes are the header */
+      int pos = headerLength;
+
+      /*
+       * read the ServerURL
+       */
+      int length = getNextLength(in, pos);
+      serverURL = new String(in, pos, length, "UTF-8");
+      pos += length +1;
+
+      /*
+       * read the maxReceiveDelay
+       */
+      length = getNextLength(in, pos);
+      maxReceiveDelay = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      /*
+       * read the maxReceiveQueue
+       */
+      length = getNextLength(in, pos);
+      maxReceiveQueue = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      /*
+       * read the maxSendDelay
+       */
+      length = getNextLength(in, pos);
+      maxSendDelay = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      /*
+       * read the maxSendQueue
+       */
+      length = getNextLength(in, pos);
+      maxSendQueue = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      /*
+       * read the windowSize
+       */
+      length = getNextLength(in, pos);
+      windowSize = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      /*
+       * read the heartbeatInterval
+       */
+      length = getNextLength(in, pos);
+      heartbeatInterval = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      /*
+       * read the sslEncryption setting
+       */
+      length = getNextLength(in, pos);
+      sslEncryption = Boolean.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      // Read the ServerState
+      // Caution: ServerState MUST be the last field. Because ServerState can
+      // contain null character (string termination of sererid string ..) it
+      // cannot be decoded using getNextLength() like the other fields. The
+      // only way is to rely on the end of the input buffer : and that forces
+      // the ServerState to be the last. This should be changed and we want to
+      // have more than one ServerState field.
+      serverState = new ServerState(in, pos, in.length - 1);
+
+    } catch (UnsupportedEncodingException e)
+    {
+      throw new DataFormatException("UTF-8 is not supported by this jvm.");
+    }
+  }
+
+  /**
+   * get the Server URL from the message.
+   * @return the server URL
+   */
+  public String getServerURL()
+  {
+    return serverURL;
+  }
+
+  /**
+   * Get the maxReceiveDelay.
+   * @return Returns the maxReceiveDelay.
+   */
+  public int getMaxReceiveDelay()
+  {
+    return maxReceiveDelay;
+  }
+
+  /**
+   * Get the maxReceiveQueue.
+   * @return Returns the maxReceiveQueue.
+   */
+  public int getMaxReceiveQueue()
+  {
+    return maxReceiveQueue;
+  }
+
+  /**
+   * Get the maxSendDelay.
+   * @return Returns the maxSendDelay.
+   */
+  public int getMaxSendDelay()
+  {
+    return maxSendDelay;
+  }
+
+  /**
+   * Get the maxSendQueue.
+   * @return Returns the maxSendQueue.
+   */
+  public int getMaxSendQueue()
+  {
+    return maxSendQueue;
+  }
+
+  /**
+   * Get the ServerState.
+   * @return The ServerState.
+   */
+  public ServerState getServerState()
+  {
+    return serverState;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public byte[] getBytes()
+  {
+    try {
+      byte[] byteServerUrl = serverURL.getBytes("UTF-8");
+      byte[] byteMaxRecvDelay =
+                     String.valueOf(maxReceiveDelay).getBytes("UTF-8");
+      byte[] byteMaxRecvQueue =
+                     String.valueOf(maxReceiveQueue).getBytes("UTF-8");
+      byte[] byteMaxSendDelay =
+                     String.valueOf(maxSendDelay).getBytes("UTF-8");
+      byte[] byteMaxSendQueue =
+                     String.valueOf(maxSendQueue).getBytes("UTF-8");
+      byte[] byteWindowSize =
+                     String.valueOf(windowSize).getBytes("UTF-8");
+      byte[] byteHeartbeatInterval =
+                     String.valueOf(heartbeatInterval).getBytes("UTF-8");
+      byte[] byteSSLEncryption =
+                     String.valueOf(sslEncryption).getBytes("UTF-8");
+      byte[] byteServerState = serverState.getBytes();
+
+      int length = byteServerUrl.length + 1 +
+                   byteMaxRecvDelay.length + 1 +
+                   byteMaxRecvQueue.length + 1 +
+                   byteMaxSendDelay.length + 1 +
+                   byteMaxSendQueue.length + 1 +
+                   byteWindowSize.length + 1 +
+                   byteHeartbeatInterval.length + 1 +
+                   byteSSLEncryption.length + 1 +
+                   byteServerState.length + 1;
+
+      /* encode the header in a byte[] large enough to also contain the mods */
+      byte resultByteArray[] = encodeHeader(MSG_TYPE_START_ECL, length);
+      int pos = headerLength;
+
+      pos = addByteArray(byteServerUrl, resultByteArray, pos);
+
+      pos = addByteArray(byteMaxRecvDelay, resultByteArray, pos);
+
+      pos = addByteArray(byteMaxRecvQueue, resultByteArray, pos);
+
+      pos = addByteArray(byteMaxSendDelay, resultByteArray, pos);
+
+      pos = addByteArray(byteMaxSendQueue, resultByteArray, pos);
+
+      pos = addByteArray(byteWindowSize, resultByteArray, pos);
+
+      pos = addByteArray(byteHeartbeatInterval, resultByteArray, pos);
+
+      pos = addByteArray(byteSSLEncryption, resultByteArray, pos);
+
+      pos = addByteArray(byteServerState, resultByteArray, pos);
+
+      return resultByteArray;
+    }
+    catch (UnsupportedEncodingException e)
+    {
+      return null;
+    }
+  }
+
+  /**
+   * Get the window size for the ldap server that created the message.
+   *
+   * @return The window size for the ldap server that created the message.
+   */
+  public int getWindowSize()
+  {
+    return windowSize;
+  }
+
+  /**
+   * Get the heartbeat interval requested by the ldap server that created the
+   * message.
+   *
+   * @return The heartbeat interval requested by the ldap server that created
+   * the message.
+   */
+  public long getHeartbeatInterval()
+  {
+    return heartbeatInterval;
+  }
+
+  /**
+   * Get the SSL encryption value for the ldap server that created the
+   * message.
+   *
+   * @return The SSL encryption value for the ldap server that created the
+   *         message.
+   */
+  public boolean getSSLEncryption()
+  {
+    return sslEncryption;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return this.getClass().getCanonicalName() + " content: " +
+      "\nprotocolVersion: " + protocolVersion +
+      "\ngenerationId: " + generationId +
+      "\ngroupId: " + groupId +
+      "\nheartbeatInterval: " + heartbeatInterval +
+      "\nmaxReceiveDelay: " + maxReceiveDelay +
+      "\nmaxReceiveQueue: " + maxReceiveQueue +
+      "\nmaxSendDelay: " + maxSendDelay +
+      "\nmaxSendQueue: " + maxSendQueue +
+      "\nserverState: " + serverState +
+      "\nserverURL: " + serverURL +
+      "\nsslEncryption: " + sslEncryption +
+      "\nwindowSize: " + windowSize;
+  }
+  }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java
index f5679cc..33720f3 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.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.protocol;
 
@@ -208,9 +208,13 @@
       sslEncryption = Boolean.valueOf(new String(in, pos, length, "UTF-8"));
       pos += length +1;
 
-      /*
-       * read the ServerState
-       */
+      // Read the ServerState
+      // Caution: ServerState MUST be the last field. Because ServerState can
+      // contain null character (string termination of sererid string ..) it
+      // cannot be decoded using getNextLength() like the other fields. The
+      // only way is to rely on the end of the input buffer : and that forces
+      // the ServerState to be the last. This should be changed and we want to
+      // have more than one ServerState field.
       serverState = new ServerState(in, pos, in.length - 1);
 
     } catch (UnsupportedEncodingException e)
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
new file mode 100644
index 0000000..b0bedc8
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java
@@ -0,0 +1,432 @@
+/*
+ * 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.protocol;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.zip.DataFormatException;
+
+import org.opends.server.replication.common.ChangeNumber;
+
+
+/**
+ * This class specifies the parameters of a search request on the ECL.
+ * It is used as an interface between the requestor (plugin part)
+ * - either as an han
+ */
+public class StartECLSessionMsg extends ReplicationMsg
+{
+
+  /**
+   * This specifies that the ECL is requested from a provided cookie value
+   * defined as a MultiDomainServerState.
+   */
+  public final static short REQUEST_TYPE_FROM_COOKIE = 0;
+
+  /**
+   * This specifies that the ECL is requested from a provided interval
+   * of change numbers (as defined by draft-good-ldap-changelog [CHANGELOG]
+   * and NOT replication change numbers).
+   * TODO: not yet implemented
+   */
+  public final static short REQUEST_TYPE_FROM_DRAFT_CHANGE_NUMBER = 1;
+
+  /**
+   * This specifies that the ECL is requested ony for the entry that have
+   * a repl change number matching the provided one.
+   * TODO: not yet implemented
+   */
+  public final static short REQUEST_TYPE_EQUALS_REPL_CHANGE_NUMBER = 2;
+
+
+
+  /**
+   * This specifies that the request on the ECL is a PERSISTENT search
+   * with changesOnly = false.
+   */
+  public final static short PERSISTENT = 0;
+
+  /**
+   * This specifies that the request on the ECL is a NOT a PERSISTENT search.
+   */
+  public final static short NON_PERSISTENT = 1;
+
+  /**
+   * This specifies that the request on the ECL is a PERSISTENT search
+   * with changesOnly = false.
+   */
+  public final static short PERSISTENT_CHANGES_ONLY = 2;;
+
+
+
+  // The type of request as defined by REQUEST_TYPE_...
+  private short  eclRequestType;
+
+  // When eclRequestType = FROM_COOKIE,
+  // specifies the provided cookie value.
+  private String crossDomainServerState = "";
+
+  // When eclRequestType = FROM_CHANGE_NUMBER,
+  // specifies the provided change number first and last - [CHANGELOG]
+  private int    firstDraftChangeNumber = -1;
+  private int    lastDraftChangeNumber = -1;
+
+  // When eclRequestType = EQUALS_REPL_CHANGE_NUMBER,
+  // specifies the provided replication change number.
+  private ChangeNumber changeNumber;
+
+  // Specifies whether the search is persistent and changesOnly
+  private short  isPersistent;
+
+  // A string helping debuging and tracing the client operation related when
+  // processing, on the RS side, a request on the ECL.
+  private String operationId = "";
+
+  // Excluded domains
+  private ArrayList<String> excludedServiceIDs = new ArrayList<String>();
+
+  /**
+   * Creates a new StartSessionMsg message from its encoded form.
+   *
+   * @param in The byte array containing the encoded form of the message.
+   * @throws java.util.zip.DataFormatException If the byte array does not
+   * contain a valid encoded form of the message.
+   */
+  public StartECLSessionMsg(byte[] in) throws DataFormatException
+  {
+    /*
+     * The message is stored in the form:
+     * <message type><status><assured flag><assured mode><safe data level>
+     * <list of referrals urls>
+     * (each referral url terminates with 0)
+     */
+
+    try
+    {
+      /* first bytes are the header */
+      int pos = 0;
+
+      /* first byte is the type */
+      if (in.length < 1 || in[pos++] != MSG_TYPE_START_ECL_SESSION)
+      {
+        throw new DataFormatException(
+          "Input is not a valid " + this.getClass().getCanonicalName());
+      }
+
+      // start mode
+      int length = getNextLength(in, pos);
+      eclRequestType = Short.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      // sequenceNumber
+      length = getNextLength(in, pos);
+      firstDraftChangeNumber =
+        Integer.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      // stopSequenceNumber
+      length = getNextLength(in, pos);
+      lastDraftChangeNumber =
+        Integer.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length +1;
+
+      // replication changeNumber
+      length = getNextLength(in, pos);
+      String changenumberStr = new String(in, pos, length, "UTF-8");
+      pos += length + 1;
+      changeNumber = new ChangeNumber(changenumberStr);
+
+      // persistentSearch mode
+      length = getNextLength(in, pos);
+      isPersistent = Short.valueOf(new String(in, pos, length, "UTF-8"));
+      pos += length + 1;
+
+      // generalized state
+      length = getNextLength(in, pos);
+      crossDomainServerState = new String(in, pos, length, "UTF-8");
+      pos += length + 1;
+
+      // operation id
+      length = getNextLength(in, pos);
+      operationId = new String(in, pos, length, "UTF-8");
+      pos += length + 1;
+
+      // excluded DN
+      length = getNextLength(in, pos);
+      String excludedDNsString = new String(in, pos, length, "UTF-8");
+      if (excludedDNsString.length()>0)
+      {
+        String[] excludedDNsStr = excludedDNsString.split(";");
+        for (String excludedDNStr : excludedDNsStr)
+        {
+          this.excludedServiceIDs.add(excludedDNStr);
+        }
+      }
+      pos += length + 1;
+
+    } catch (UnsupportedEncodingException e)
+    {
+      throw new DataFormatException("UTF-8 is not supported by this jvm.");
+    } catch (IllegalArgumentException e)
+    {
+      throw new DataFormatException(e.getMessage());
+    }
+  }
+
+  /**
+   * Creates a new StartSessionMsg message with the given required parameters.
+   */
+  public StartECLSessionMsg()
+  {
+    changeNumber = new ChangeNumber((short)0,0,(short)0);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public byte[] getBytes()
+  {
+    String excludedSIDsString = new String();
+    for (String excludedServiceID : excludedServiceIDs)
+    {
+      excludedSIDsString = excludedSIDsString.concat(excludedServiceID+";");
+    }
+
+    try
+    {
+      byte[] byteMode =
+        Short.toString(eclRequestType).getBytes("UTF-8");
+      byte[] byteSequenceNumber =
+        String.valueOf(firstDraftChangeNumber).getBytes("UTF-8");
+      byte[] byteStopSequenceNumber =
+        String.valueOf(lastDraftChangeNumber).getBytes("UTF-8");
+      byte[] byteChangeNumber =
+        changeNumber.toString().getBytes("UTF-8");
+      byte[] bytePsearch =
+        Short.toString(isPersistent).getBytes();
+      byte[] byteGeneralizedState =
+        String.valueOf(crossDomainServerState).getBytes("UTF-8");
+      byte[] byteOperationId =
+        String.valueOf(operationId).getBytes("UTF-8");
+      byte[] byteExcludedDNs =
+        String.valueOf(excludedSIDsString).getBytes("UTF-8");
+
+      int length =
+        byteMode.length + 1 +
+        byteSequenceNumber.length + 1 +
+        byteStopSequenceNumber.length + 1 +
+        byteChangeNumber.length + 1 +
+        bytePsearch.length + 1 +
+        byteGeneralizedState.length + 1 +
+        byteOperationId.length + 1 +
+        byteExcludedDNs.length + 1 +
+        1;
+
+      byte[] resultByteArray = new byte[length];
+      int pos = 0;
+      resultByteArray[pos++] = MSG_TYPE_START_ECL_SESSION;
+      pos = addByteArray(byteMode, resultByteArray, pos);
+      pos = addByteArray(byteSequenceNumber, resultByteArray, pos);
+      pos = addByteArray(byteStopSequenceNumber, resultByteArray, pos);
+      pos = addByteArray(byteChangeNumber, resultByteArray, pos);
+      pos = addByteArray(bytePsearch, resultByteArray, pos);
+      pos = addByteArray(byteGeneralizedState, resultByteArray, pos);
+      pos = addByteArray(byteOperationId, resultByteArray, pos);
+      pos = addByteArray(byteExcludedDNs, resultByteArray, pos);
+      return resultByteArray;
+
+    } catch (IOException e)
+    {
+      // never happens
+      return null;
+    }
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return (this.getClass().getCanonicalName() + " [" +
+            " requestType="+ eclRequestType +
+            " persistentSearch="       + isPersistent +
+            " changeNumber="           + changeNumber +
+            " firstDraftChangeNumber=" + firstDraftChangeNumber +
+            " lastDraftChangeNumber="  + lastDraftChangeNumber +
+            " generalizedState="       + crossDomainServerState +
+            " operationId="            + operationId +
+            " excludedDNs="            + excludedServiceIDs + "]");
+  }
+
+  /**
+   * Getter on the changer number start.
+   * @return the changer number start.
+   */
+  public int getFirstDraftChangeNumber()
+  {
+    return firstDraftChangeNumber;
+  }
+
+  /**
+   * Getter on the changer number stop.
+   * @return the change number stop.
+   */
+  public int getLastDraftChangeNumber()
+  {
+    return lastDraftChangeNumber;
+  }
+
+  /**
+   * Setter on the first changer number (as defined by [CHANGELOG]).
+   * @param firstDraftChangeNumber the provided first change number.
+   */
+  public void setFirstDraftChangeNumber(int firstDraftChangeNumber)
+  {
+    this.firstDraftChangeNumber = firstDraftChangeNumber;
+  }
+
+  /**
+   * Setter on the last changer number (as defined by [CHANGELOG]).
+   * @param lastDraftChangeNumber the provided last change number.
+   */
+  public void setLastDraftChangeNumber(int lastDraftChangeNumber)
+  {
+    this.lastDraftChangeNumber = lastDraftChangeNumber;
+  }
+
+  /**
+   * Getter on the replication change number.
+   * @return the replication change number.
+   */
+  public ChangeNumber getChangeNumber()
+  {
+    return changeNumber;
+  }
+
+  /**
+   * Setter on the replication change number.
+   * @param changeNumber the provided replication change number.
+   */
+  public void setChangeNumber(ChangeNumber changeNumber)
+  {
+    this.changeNumber = changeNumber;
+  }
+  /**
+   * Getter on the type of request.
+   * @return the type of request.
+   */
+  public short getECLRequestType()
+  {
+    return eclRequestType;
+  }
+
+  /**
+   * Setter on the type of request.
+   * @param eclRequestType the provided type of request.
+   */
+  public void setECLRequestType(short eclRequestType)
+  {
+    this.eclRequestType = eclRequestType;
+  }
+
+  /**
+   * Getter on the persistent property of the search request on the ECL.
+   * @return the persistent property.
+   */
+  public short isPersistent()
+  {
+    return this.isPersistent;
+  }
+
+  /**
+   * Setter on the persistent property of the search request on the ECL.
+   * @param isPersistent the provided persistent property.
+   */
+  public void setPersistent(short isPersistent)
+  {
+    this.isPersistent = isPersistent;
+  }
+
+  /**
+   * Getter of the cross domain server state.
+   * @return the cross domain server state.
+   */
+  public String getCrossDomainServerState()
+  {
+    return this.crossDomainServerState;
+  }
+
+  /**
+   * Setter of the cross domain server state.
+   * @param crossDomainServerState the provided cross domain server state.
+   */
+  public void setCrossDomainServerState(String crossDomainServerState)
+  {
+    this.crossDomainServerState = crossDomainServerState;
+  }
+
+  /**
+   * Setter of the operation id.
+   * @param operationId The provided opration id.
+   */
+  public void setOperationId(String operationId)
+  {
+    this.operationId = operationId;
+  }
+
+  /**
+   * Getter on the operation id.
+   * @return the operation id.
+   */
+  public String getOperationId()
+  {
+    return this.operationId;
+  }
+
+  /**
+   * Getter on the list of excluded ServiceIDs.
+   * @return the list of excluded ServiceIDs.
+   */
+  public ArrayList<String> getExcludedServiceIDs()
+  {
+    return this.excludedServiceIDs;
+  }
+
+  /**
+   * Setter on the list of excluded ServiceIDs.
+   * @param excludedServiceIDs the provided list of excluded ServiceIDs.
+   */
+  public void setExcludedDNs(ArrayList<String> excludedServiceIDs)
+  {
+    this.excludedServiceIDs = excludedServiceIDs;
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/DataServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DataServerHandler.java
new file mode 100644
index 0000000..c5b6b1d
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DataServerHandler.java
@@ -0,0 +1,724 @@
+/*
+ * 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.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.replication.common.StatusMachine.*;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.DataFormatException;
+
+import org.opends.messages.Message;
+import org.opends.server.replication.common.AssuredMode;
+import org.opends.server.replication.common.DSInfo;
+import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.common.ServerStatus;
+import org.opends.server.replication.common.StatusMachine;
+import org.opends.server.replication.common.StatusMachineEvent;
+import org.opends.server.replication.protocol.*;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeBuilder;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+
+/**
+ * This class defines a server handler, which handles all interaction with a
+ * peer server (RS or DS).
+ */
+public class DataServerHandler extends ServerHandler
+{
+  // Status of this DS (only used if this server handler represents a DS)
+  private ServerStatus status = ServerStatus.INVALID_STATUS;
+
+  // Referrals URLs this DS is exporting
+  private List<String> refUrls = new ArrayList<String>();
+  // Assured replication enabled on DS or not
+  private boolean assuredFlag = false;
+  // DS assured mode (relevant if assured replication enabled)
+  private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE;
+  // DS safe data level (relevant if assured mode is safe data)
+  private byte safeDataLevel = (byte) -1;
+
+  /**
+   * Creates a new data server handler.
+   * @param session The session opened with the remote data server.
+   * @param queueSize The queue size.
+   * @param replicationServerURL The URL of the hosting RS.
+   * @param replicationServerId The serverID of the hosting RS.
+   * @param replicationServer The hosting RS.
+   * @param rcvWindowSize The receiving window size.
+   */
+  public DataServerHandler(
+      ProtocolSession session,
+      int queueSize,
+      String replicationServerURL,
+      short replicationServerId,
+      ReplicationServer replicationServer,
+      int rcvWindowSize)
+  {
+    super(session, queueSize, replicationServerURL, replicationServerId,
+        replicationServer, rcvWindowSize);
+  }
+
+  /**
+   * Order the peer DS server to change his status or close the connection
+   * according to the requested new generation id.
+   * @param newGenId The new generation id to take into account
+   * @throws IOException If IO error occurred.
+   */
+  public void changeStatusForResetGenId(long newGenId)
+  throws IOException
+  {
+    StatusMachineEvent event = null;
+
+    if (newGenId == -1)
+    {
+      // The generation id is being made invalid, let's put the DS
+      // into BAD_GEN_ID_STATUS
+      event = StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT;
+    } else
+    {
+      if (newGenId == generationId)
+      {
+        if (status == ServerStatus.BAD_GEN_ID_STATUS)
+        {
+          // This server has the good new reference generation id.
+          // Close connection with him to force his reconnection: DS will
+          // reconnect in NORMAL_STATUS or DEGRADED_STATUS.
+
+          if (debugEnabled())
+          {
+            TRACER.debugInfo(
+                "In RS " +
+                replicationServerDomain.getReplicationServer().getServerId() +
+                ". Closing connection to DS " + getServerId() +
+                " for baseDn " + getServiceId() +
+                " to force reconnection as new local" +
+                " generationId and remote one match and DS is in bad gen id: " +
+                newGenId);
+          }
+
+          // Connection closure must not be done calling RSD.stopHandler() as it
+          // would rewait the RSD lock that we already must have entering this
+          // method. This would lead to a reentrant lock which we do not want.
+          // So simply close the session, this will make the hang up appear
+          // after the reader thread that took the RSD lock realeases it.
+          try
+          {
+            if (session != null)
+              session.close();
+          } catch (IOException e)
+          {
+            // ignore
+          }
+
+          // NOT_CONNECTED_STATUS is the last one in RS session life: handler
+          // will soon disappear after this method call...
+          status = ServerStatus.NOT_CONNECTED_STATUS;
+          return;
+        } else
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugInfo(
+                "In RS " +
+                replicationServerDomain.getReplicationServer().getServerId() +
+                ". DS " + getServerId() + " for baseDn " + getServiceId() +
+                " has already generation id " + newGenId +
+            " so no ChangeStatusMsg sent to him.");
+          }
+          return;
+        }
+      } else
+      {
+        // This server has a bad generation id compared to new reference one,
+        // let's put it into BAD_GEN_ID_STATUS
+        event = StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT;
+      }
+    }
+
+    if ((event == StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT) &&
+        (status == ServerStatus.FULL_UPDATE_STATUS))
+    {
+      // Prevent useless error message (full update status cannot lead to bad
+      // gen status)
+      Message message = NOTE_BAD_GEN_ID_IN_FULL_UPDATE.get(
+          Short.toString(replicationServerDomain.
+              getReplicationServer().getServerId()),
+              getServiceId().toString(),
+              Short.toString(serverId),
+              Long.toString(generationId),
+              Long.toString(newGenId));
+      logError(message);
+      return;
+    }
+
+    ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
+
+    if (newStatus == ServerStatus.INVALID_STATUS)
+    {
+      Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(getServiceId().toString(),
+          Short.toString(serverId), status.toString(), event.toString());
+      logError(msg);
+      return;
+    }
+
+    // Send message requesting to change the DS status
+    ChangeStatusMsg csMsg = new ChangeStatusMsg(newStatus,
+        ServerStatus.INVALID_STATUS);
+
+    if (debugEnabled())
+    {
+      TRACER.debugInfo(
+          "In RS " +
+          replicationServerDomain.getReplicationServer().getServerId() +
+          " Sending change status for reset gen id to " + getServerId() +
+          " for baseDn " + getServiceId() + ":\n" + csMsg);
+    }
+
+    session.publish(csMsg);
+
+    status = newStatus;
+  }
+
+  /**
+   * Change the status according to the event generated from the status
+   * analyzer.
+   * @param event The event to be used for new status computation
+   * @return The new status of the DS
+   * @throws IOException When raised by the underlying session
+   */
+  public ServerStatus changeStatusFromStatusAnalyzer(StatusMachineEvent event)
+  throws IOException
+  {
+    // Check state machine allows this new status (Sanity check)
+    ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
+    if (newStatus == ServerStatus.INVALID_STATUS)
+    {
+      Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(getServiceId().toString(),
+          Short.toString(serverId), status.toString(), event.toString());
+      logError(msg);
+      // Status analyzer must only change from NORMAL_STATUS to DEGRADED_STATUS
+      // and vice versa. We may are being trying to change the status while for
+      // instance another status has just been entered: e.g a full update has
+      // just been engaged. In that case, just ignore attempt to change the
+      // status
+      return newStatus;
+    }
+
+    // Send message requesting to change the DS status
+    ChangeStatusMsg csMsg = new ChangeStatusMsg(newStatus,
+        ServerStatus.INVALID_STATUS);
+
+    if (debugEnabled())
+    {
+      TRACER.debugInfo(
+          "In RS " +
+          replicationServerDomain.getReplicationServer().getServerId() +
+          " Sending change status from status analyzer to " + getServerId() +
+          " for baseDn " + getServiceId() + ":\n" + csMsg);
+    }
+
+    session.publish(csMsg);
+
+    status = newStatus;
+
+    return newStatus;
+  }
+
+  private void createStatusAnalyzer()
+  {
+    if (!replicationServerDomain.isRunningStatusAnalyzer())
+    {
+      replicationServerDomain.startStatusAnalyzer();
+    }
+  }
+
+  /**
+   * Retrieves a set of attributes containing monitor data that should be
+   * returned to the client if the corresponding monitor entry is requested.
+   *
+   * @return  A set of attributes containing monitor data that should be
+   *          returned to the client if the corresponding monitor entry is
+   *          requested.
+   */
+  @Override
+  public ArrayList<Attribute> getMonitorData()
+  {
+    // Get the generic ones
+    ArrayList<Attribute> attributes = super.getMonitorData();
+
+    // Add the specific DS ones
+    attributes.add(Attributes.create("replica", serverURL));
+    attributes.add(Attributes.create("connected-to",
+        this.replicationServerDomain.getReplicationServer()
+        .getMonitorInstanceName()));
+
+    try
+    {
+      MonitorData md;
+      md = replicationServerDomain.computeMonitorData();
+
+      // Oldest missing update
+      Long approxFirstMissingDate = md.getApproxFirstMissingDate(serverId);
+      if ((approxFirstMissingDate != null) && (approxFirstMissingDate > 0))
+      {
+        Date date = new Date(approxFirstMissingDate);
+        attributes.add(Attributes.create(
+            "approx-older-change-not-synchronized", date.toString()));
+        attributes.add(Attributes.create(
+            "approx-older-change-not-synchronized-millis", String
+            .valueOf(approxFirstMissingDate)));
+      }
+
+      // Missing changes
+      long missingChanges = md.getMissingChanges(serverId);
+      attributes.add(Attributes.create("missing-changes", String
+          .valueOf(missingChanges)));
+
+      // Replication delay
+      long delay = md.getApproxDelay(serverId);
+      attributes.add(Attributes.create("approximate-delay", String
+          .valueOf(delay)));
+
+      /* get the Server State */
+      AttributeBuilder builder = new AttributeBuilder("server-state");
+      ServerState state = md.getLDAPServerState(serverId);
+      if (state != null)
+      {
+        for (String str : state.toStringSet())
+        {
+          builder.add(str);
+        }
+        attributes.add(builder.toAttribute());
+      }
+
+    }
+    catch (Exception e)
+    {
+      Message message =
+        ERR_ERROR_RETRIEVING_MONITOR_DATA.get(stackTraceToSingleLineString(e));
+      // We failed retrieving the monitor data.
+      attributes.add(Attributes.create("error", message.toString()));
+    }
+    return attributes;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getMonitorInstanceName()
+  {
+    String str = serverURL + " " + String.valueOf(serverId);
+
+    return "Connected Replica " + str +
+    ",cn=" + replicationServerDomain.getMonitorInstanceName();
+  }
+
+  /**
+   * Gets the status of the connected DS.
+   * @return The status of the connected DS.
+   */
+  public ServerStatus getStatus()
+  {
+    return status;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isDataServer()
+  {
+    return true;
+  }
+
+  /**
+   * Process message of a remote server changing his status.
+   * @param csMsg The message containing the new status
+   * @return The new server status of the DS
+   */
+  public ServerStatus processNewStatus(ChangeStatusMsg csMsg)
+  {
+    // Get the status the DS just entered
+    ServerStatus reqStatus = csMsg.getNewStatus();
+    // Translate new status to a state machine event
+    StatusMachineEvent event = StatusMachineEvent.statusToEvent(reqStatus);
+    if (event == StatusMachineEvent.INVALID_EVENT)
+    {
+      Message msg = ERR_RS_INVALID_NEW_STATUS.get(reqStatus.toString(),
+          getServiceId().toString(), Short.toString(serverId));
+      logError(msg);
+      return ServerStatus.INVALID_STATUS;
+    }
+
+    // Check state machine allows this new status
+    ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
+    if (newStatus == ServerStatus.INVALID_STATUS)
+    {
+      Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(getServiceId().toString(),
+          Short.toString(serverId), status.toString(), event.toString());
+      logError(msg);
+      return ServerStatus.INVALID_STATUS;
+    }
+
+    status = newStatus;
+
+    return status;
+  }
+
+  /**
+   * Processes a start message received from a remote data server.
+   * @param serverStartMsg The provided start message received.
+   * @return flag specifying whether the remote server requests encryption.
+   * @throws DirectoryException raised when an error occurs.
+   */
+  public boolean processStartFromRemote(ServerStartMsg serverStartMsg)
+  throws DirectoryException
+  {
+    generationId = serverStartMsg.getGenerationId();
+    protocolVersion = ProtocolVersion.minWithCurrent(
+        serverStartMsg.getVersion());
+    serverId = serverStartMsg.getServerId();
+    serverURL = serverStartMsg.getServerURL();
+    groupId = serverStartMsg.getGroupId();
+    maxReceiveDelay = serverStartMsg.getMaxReceiveDelay();
+    maxReceiveQueue = serverStartMsg.getMaxReceiveQueue();
+    maxSendDelay = serverStartMsg.getMaxSendDelay();
+    maxSendQueue = serverStartMsg.getMaxSendQueue();
+    heartbeatInterval = serverStartMsg.getHeartbeatInterval();
+
+    // generic stuff
+    setServiceIdAndDomain(serverStartMsg.getBaseDn());
+    setInitialServerState(serverStartMsg.getServerState());
+    setSendWindowSize(serverStartMsg.getWindowSize());
+
+    if (maxReceiveQueue > 0)
+      restartReceiveQueue = (maxReceiveQueue > 1000 ? maxReceiveQueue -
+          200 : maxReceiveQueue * 8 / 10);
+    else
+      restartReceiveQueue = 0;
+
+    if (maxSendQueue > 0)
+      restartSendQueue =
+        (maxSendQueue > 1000 ? maxSendQueue - 200 : maxSendQueue * 8 /
+            10);
+    else
+      restartSendQueue = 0;
+
+    if (maxReceiveDelay > 0)
+      restartReceiveDelay = (maxReceiveDelay > 10 ? maxReceiveDelay - 1
+          : maxReceiveDelay);
+    else
+      restartReceiveDelay = 0;
+
+    if (maxSendDelay > 0)
+      restartSendDelay =
+        (maxSendDelay > 10 ? maxSendDelay - 1 : maxSendDelay);
+    else
+      restartSendDelay = 0;
+
+    if (heartbeatInterval < 0)
+    {
+      heartbeatInterval = 0;
+    }
+    return serverStartMsg.getSSLEncryption();
+  }
+
+  /**
+   * Registers this handler into its related domain and notifies the domain
+   * about the new DS.
+   */
+  public void registerIntoDomain()
+  {
+    // Alright, connected with new DS: store handler.
+    Map<Short, DataServerHandler> connectedDSs =
+      replicationServerDomain.getConnectedDSs();
+    connectedDSs.put(serverId, this);
+
+    // Tell peer DSs a new DS just connected to us
+    // No need to resend topo msg to this just new DS so not null
+    // argument
+    replicationServerDomain.buildAndSendTopoInfoToDSs(this);
+    // Tell peer RSs a new DS just connected to us
+    replicationServerDomain.buildAndSendTopoInfoToRSs();
+  }
+
+  // Send our own TopologyMsg to DS
+  private TopologyMsg sendTopoToRemoteDS()
+  throws IOException
+  {
+    TopologyMsg outTopoMsg = replicationServerDomain.createTopologyMsgForDS(
+        this.serverId);
+    session.publish(outTopoMsg);
+    return outTopoMsg;
+  }
+  /**
+   * Starts the handler from a remote ServerStart message received from
+   * the remote data server.
+   * @param inServerStartMsg The provided ServerStart message received.
+   */
+  public void startFromRemoteDS(ServerStartMsg inServerStartMsg)
+  {
+    try
+    {
+      // initializations
+      localGenerationId = -1;
+      oldGenerationId = -100;
+
+      // processes the ServerStart message received
+      boolean sessionInitiatorSSLEncryption =
+        processStartFromRemote(inServerStartMsg);
+
+      // Get or Create the ReplicationServerDomain
+      replicationServerDomain = getDomain(true);
+      localGenerationId = replicationServerDomain.getGenerationId();
+      oldGenerationId = localGenerationId;
+
+      // Hack to be sure that if a server disconnects and reconnect, we
+      // let the reader thread see the closure and cleanup any reference
+      // to old connection
+      replicationServerDomain.
+      waitDisconnection(inServerStartMsg.getServerId());
+
+      // Duplicate server ?
+      if (!replicationServerDomain.checkForDuplicateDS(this))
+      {
+        abortStart(null);
+        return;
+      }
+
+      // lock with no timeout
+      lockDomain(false);
+
+      //
+      ReplServerStartMsg outReplServerStartMsg = null;
+      try
+      {
+        outReplServerStartMsg = sendStartToRemote((short)-1);
+
+        // log
+        logStartHandshakeRCVandSND(inServerStartMsg, outReplServerStartMsg);
+
+        // The session initiator decides whether to use SSL.
+        // Until here session is encrypted then it depends on the negociation
+        if (!sessionInitiatorSSLEncryption)
+          session.stopEncryption();
+
+        // wait and process StartSessionMsg from remote RS
+        StartSessionMsg inStartSessionMsg =
+          waitAndProcessStartSessionFromRemoteDS();
+
+        // Send our own TopologyMsg to remote RS
+        TopologyMsg outTopoMsg = sendTopoToRemoteDS();
+
+        logStartSessionHandshake(inStartSessionMsg, outTopoMsg);
+      }
+      catch(IOException e)
+      {
+        // We do not want polluting error log if error is due to normal session
+        // aborted after handshake phase one from a DS that is searching for
+        // best suitable RS.
+
+        // don't log a poluting error when connection aborted
+        // from a DS that wanted only to perform handshake phase 1 in order
+        // to determine the best suitable RS:
+        // 1) -> ServerStartMsg
+        // 2) <- ReplServerStartMsg
+        // 3) connection closure
+
+        throw new DirectoryException(ResultCode.OTHER, null, null);
+      }
+      catch (NotSupportedOldVersionPDUException e)
+      {
+        // We do not need to support DS V1 connection, we just accept RS V1
+        // connection:
+        // We just trash the message, log the event for debug purpose and close
+        // the connection
+        throw new DirectoryException(ResultCode.OTHER, null, null);
+      }
+      catch (Exception e)
+      {
+        // We do not need to support DS V1 connection, we just accept RS V1
+        // connection:
+        // We just trash the message, log the event for debug purpose and close
+        // the connection
+        throw new DirectoryException(ResultCode.OTHER, null, null);
+      }
+
+      // Create the status analyzer for the domain if not already started
+      createStatusAnalyzer();
+
+      registerIntoDomain();
+
+      super.finalizeStart();
+
+    }
+    catch(DirectoryException de)
+    {
+      abortStart(de.getMessageObject());
+    }
+    catch(Exception e)
+    {
+      abortStart(null);
+    }
+    finally
+    {
+      if ((replicationServerDomain != null) &&
+          replicationServerDomain.hasLock())
+        replicationServerDomain.release();
+    }
+  }
+  /**
+   * Creates a DSInfo structure representing this remote DS.
+   * @return The DSInfo structure representing this remote DS
+   */
+  public DSInfo toDSInfo()
+  {
+    DSInfo dsInfo = new DSInfo(serverId, replicationServerId, generationId,
+        status, assuredFlag, assuredMode, safeDataLevel, groupId, refUrls);
+
+    return dsInfo;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    String localString;
+    if (serverId != 0)
+    {
+      localString = "Directory Server ";
+      localString += serverId + " " + serverURL + " " + getServiceId();
+    } else
+      localString = "Unknown server";
+
+    return localString;
+  }
+
+  /**
+   * Wait receiving the StartSessionMsg from the remote DS and process it.
+   * @return the startSessionMsg received
+   * @throws DirectoryException
+   * @throws IOException
+   * @throws ClassNotFoundException
+   * @throws DataFormatException
+   * @throws NotSupportedOldVersionPDUException
+   */
+  private StartSessionMsg waitAndProcessStartSessionFromRemoteDS()
+  throws DirectoryException, IOException, ClassNotFoundException,
+  DataFormatException,
+  NotSupportedOldVersionPDUException
+  {
+    ReplicationMsg msg = null;
+    msg = session.receive();
+
+    if (!(msg instanceof StartSessionMsg))
+    {
+      Message message = Message.raw(
+          "Protocol error: StartSessionMsg required." + msg + " received.");
+      abortStart(message);
+    }
+
+    // Process StartSessionMsg sent by remote DS
+    StartSessionMsg startSessionMsg = (StartSessionMsg) msg;
+
+    this.status = startSessionMsg.getStatus();
+    // Sanity check: is it a valid initial status?
+    if (!isValidInitialStatus(this.status))
+    {
+      Message message = ERR_RS_INVALID_INIT_STATUS.get(
+          this.status.toString(),
+          getServiceId().toString(),
+          Short.toString(serverId));
+      throw new DirectoryException(ResultCode.OTHER, message);
+    }
+    this.refUrls = startSessionMsg.getReferralsURLs();
+    this.assuredFlag = startSessionMsg.isAssured();
+    this.assuredMode = startSessionMsg.getAssuredMode();
+    this.safeDataLevel = startSessionMsg.getSafeDataLevel();
+
+    /*
+     * If we have already a generationID set for the domain
+     * then
+     *   if the connecting replica has not the same
+     *   then it is degraded locally and notified by an error message
+     * else
+     *   we set the generationID from the one received
+     *   (unsaved yet on disk . will be set with the 1rst change
+     * received)
+     */
+    if (localGenerationId > 0)
+    {
+      if (generationId != localGenerationId)
+      {
+        Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get(
+            getServiceId(),
+            Short.toString(serverId),
+            Long.toString(generationId),
+            Long.toString(localGenerationId));
+        logError(message);
+      }
+    }
+    else
+    {
+      // We are an empty Replicationserver
+      if ((generationId > 0) && (!getServerState().isEmpty()))
+      {
+        // If the LDAP server has already sent changes
+        // it is not expected to connect to an empty RS
+        Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get(
+            getServiceId(),
+            Short.toString(serverId),
+            Long.toString(generationId),
+            Long.toString(localGenerationId));
+        logError(message);
+      }
+      else
+      {
+        // The local RS is not initialized - take the one received
+        // WARNING: Must be done before computing topo message to send
+        // to peer server as topo message must embed valid generation id
+        // for our server
+        oldGenerationId =
+          replicationServerDomain.setGenerationId(generationId, false);
+      }
+    }
+    return startSessionMsg;
+  }
+}
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
new file mode 100644
index 0000000..ec855de
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java
@@ -0,0 +1,1551 @@
+/*
+ * 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.util.StaticUtils.stackTraceToSingleLineString;
+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.Iterator;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.DataFormatException;
+
+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;
+import org.opends.server.replication.common.ServerStatus;
+import org.opends.server.replication.protocol.*;
+import org.opends.server.types.Attribute;
+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;
+
+/**
+ * This class defines a server handler, which handles all interaction with a
+ * peer replication server.
+ */
+public class ECLServerHandler extends ServerHandler
+{
+
+  // Properties filled only if remote server is a RS
+  private String serverAddressURL;
+
+  String operationId;
+
+  /**
+   * CLDomainContext : contains the state properties for the search
+   * currently being processed, by replication domain.
+   */
+  private class CLDomainContext
+  {
+    ReplicationServerDomain rsd; // the repl server domain
+    boolean active;              // is the domain still active
+    MessageHandler mh;           // the message handler associated
+    UpdateMsg nextMsg;
+    UpdateMsg nonElligiblemsg;
+    ServerState startState;
+    ServerState currentState;
+    ServerState stopState;
+
+    /**
+     * Add to the provider buffer a string representation of this object.
+     */
+    public void toString(StringBuilder buffer, int i)
+    {
+      CLDomainContext xx = clDomCtxts[i];
+      buffer.append(
+          " clDomCtxts(" + i + ") [act=" + xx.active +
+          " rsd=" + rsd +
+          " nextMsg=" + nextMsg + "(" +
+          (nextMsg != null?
+              new Date(nextMsg.getChangeNumber().getTime()).toString():"")
+              + ")" +
+          " nextNonEligibleMsg="      + nonElligiblemsg +
+          " startState=" + startState +
+          " stopState= " + stopState +
+          " currState= " + currentState + "]");
+    }
+  }
+
+  // The list of contexts by domain for the current search
+  CLDomainContext[] clDomCtxts = new CLDomainContext[0];
+
+  private void clDomCtxtsToString(String msg)
+  {
+    StringBuilder buffer = new StringBuilder();
+    buffer.append(msg+"\n");
+    for (int i=0;i<clDomCtxts.length;i++)
+    {
+      clDomCtxts[i].toString(buffer, i);
+      buffer.append("\n");
+    }
+    TRACER.debugInfo(
+        "In " + this.getName() + " clDomCtxts: " + 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();
+
+  /**
+   * Starts this handler based on a start message received from remote server.
+   * @param inECLStartMsg The start msg provided by the remote server.
+   * @return Whether the remote server requires encryption or not.
+   * @throws DirectoryException When a problem occurs.
+   */
+  public boolean processStartFromRemote(ServerStartECLMsg inECLStartMsg)
+  throws DirectoryException
+  {
+    try
+    {
+      protocolVersion = ProtocolVersion.minWithCurrent(
+          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)
+      {
+        // We support connection from a V1 RS
+        // 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)
+    {
+      Message message = Message.raw(e.getLocalizedMessage());
+      throw new DirectoryException(ResultCode.OTHER, message);
+    }
+    return inECLStartMsg.getSSLEncryption();
+  }
+
+  /**
+   * Creates a new handler object to a remote replication server.
+   * @param session The session with the remote RS.
+   * @param queueSize The queue size to manage updates to that RS.
+   * @param replicationServerURL The hosting local RS URL.
+   * @param replicationServerId The hosting local RS serverId.
+   * @param replicationServer The hosting local RS object.
+   * @param rcvWindowSize The receiving window size.
+   */
+  public ECLServerHandler(
+      ProtocolSession session,
+      int queueSize,
+      String replicationServerURL,
+      short replicationServerId,
+      ReplicationServer replicationServer,
+      int rcvWindowSize)
+  {
+    super(session, queueSize, replicationServerURL, replicationServerId,
+        replicationServer, rcvWindowSize);
+    try
+    {
+      setServiceIdAndDomain("cn=changelog");
+    }
+    catch(DirectoryException de)
+    {
+      // no chance to have a bad domain set here
+    }
+
+
+  }
+
+  /**
+   * Creates a new handler object to a remote replication server.
+   * @param replicationServerURL The hosting local RS URL.
+   * @param replicationServerId The hosting local RS serverId.
+   * @param replicationServer The hosting local RS object.
+   * @param startECLSessionMsg the start parameters.
+   * @throws DirectoryException when an errors occurs.
+   */
+  public ECLServerHandler(
+      String replicationServerURL,
+      short replicationServerId,
+      ReplicationServer replicationServer,
+      StartECLSessionMsg startECLSessionMsg)
+  throws DirectoryException
+  {
+    // FIXME:ECL queueSize is hard coded to 1 else Handler hangs for some reason
+    super(null, 1, replicationServerURL, replicationServerId,
+        replicationServer, 0);
+    try
+    {
+      setServiceIdAndDomain("cn=changelog");
+    }
+    catch(DirectoryException de)
+    {
+      // no chance to have a bad domain set here
+    }
+    this.initialize(startECLSessionMsg);
+  }
+
+  /**
+   * Starts the handler from a remote start message received from
+   * the remote server.
+   * @param inECLStartMsg The provided ReplServerStart message received.
+   */
+  public void startFromRemoteServer(ServerStartECLMsg inECLStartMsg)
+  {
+    try
+    {
+      // Process start from remote
+      boolean sessionInitiatorSSLEncryption =
+        processStartFromRemote(inECLStartMsg);
+
+      // lock with timeout
+      lockDomain(true);
+
+      // send start to remote
+      ReplServerStartMsg outReplServerStartMsg = sendStartToRemote((short)-1);
+
+      // log
+      logStartHandshakeRCVandSND(inECLStartMsg, outReplServerStartMsg);
+
+      // until here session is encrypted then it depends on the negociation
+      // The session initiator decides whether to use SSL.
+      if (!sessionInitiatorSSLEncryption)
+        session.stopEncryption();
+
+      // wait and process StartSessionMsg from remote RS
+      StartECLSessionMsg inStartECLSessionMsg =
+        waitAndProcessStartSessionECLFromRemoteServer();
+
+      logStartECLSessionHandshake(inStartECLSessionMsg);
+
+      // initialization
+      initialize(inStartECLSessionMsg);
+    }
+    catch(DirectoryException de)
+    {
+      abortStart(de.getMessageObject());
+    }
+    catch(Exception e)
+    {
+      abortStart(Message.raw(e.getLocalizedMessage()));
+    }
+    finally
+    {
+      if ((replicationServerDomain != null) &&
+          replicationServerDomain.hasLock())
+      {
+        replicationServerDomain.release();
+      }
+    }
+  }
+
+  /**
+   * Wait receiving the StartSessionMsg from the remote DS and process it.
+   * @return the startSessionMsg received
+   * @throws DirectoryException
+   * @throws IOException
+   * @throws ClassNotFoundException
+   * @throws DataFormatException
+   * @throws NotSupportedOldVersionPDUException
+   */
+  private StartECLSessionMsg waitAndProcessStartSessionECLFromRemoteServer()
+  throws DirectoryException, IOException, ClassNotFoundException,
+  DataFormatException,
+  NotSupportedOldVersionPDUException
+  {
+    ReplicationMsg msg = null;
+    msg = session.receive();
+
+    if (!(msg instanceof StartECLSessionMsg))
+    {
+      Message message = Message.raw(
+          "Protocol error: StartECLSessionMsg required." + msg + " received.");
+      abortStart(message);
+    }
+
+    // Process StartSessionMsg sent by remote DS
+    StartECLSessionMsg startECLSessionMsg = (StartECLSessionMsg) msg;
+
+    return startECLSessionMsg;
+  }
+
+  /**
+   * Initialize the handler from a provided cookie value.
+   * @param providedGeneralizedStartState The provided cookie value.
+   * @throws DirectoryException When an error is raised.
+   */
+  public void initializeCLSearchFromGenState(
+      String providedGeneralizedStartState)
+  throws DirectoryException
+  {
+    this.cLSearchCtxt.nextSeqnum = -1; // will not generate seqnum
+    initializeCLDomCtxts(providedGeneralizedStartState);
+  }
+
+  /**
+   * Initialize the context for each domain.
+   * @param providedGeneralizedStartState the provided generalized state
+   * @throws DirectoryException When an error occurs.
+   */
+  public void initializeCLDomCtxts(String providedGeneralizedStartState)
+  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()));
+    }
+
+    try
+    {
+      // Now traverse all domains and build the initial changelog context
+      Iterator<ReplicationServerDomain> rsdi = rs.getCacheIterator();
+
+      // Creates the table that will contain the real-time info by domain.
+      clDomCtxts = new CLDomainContext[rs.getCacheSize()-1
+                         -this.cLSearchCtxt.excludedServiceIDs.size()];
+      int i =0;
+      if (rsdi != null)
+      {
+        while (rsdi.hasNext())
+        {
+          // process a domain
+          ReplicationServerDomain rsd = rsdi.next();
+
+          // skip the 'unreal' changelog domain
+          if (rsd == this.replicationServerDomain)
+            continue;
+
+          // skip the excluded domains
+          boolean excluded = false;
+          for(String excludedServiceID : this.cLSearchCtxt.excludedServiceIDs)
+          {
+            if (excludedServiceID.equalsIgnoreCase(rsd.getBaseDn()))
+            {
+              excluded=true;
+              break;
+            }
+          }
+          if (excluded)
+            continue;
+
+          // Creates the context record
+          CLDomainContext newContext = new CLDomainContext();
+          newContext.active = true;
+          newContext.rsd = rsd;
+
+          if (this.cLSearchCtxt.isPersistent ==
+            StartECLSessionMsg.PERSISTENT_CHANGES_ONLY)
+          {
+            newContext.startState = rsd.getCLElligibleState();
+          }
+          else
+          {
+            newContext.startState = startStates.get(rsd.getBaseDn());
+            newContext.stopState = rsd.getCLElligibleState();
+          }
+          newContext.currentState = new ServerState();
+
+          // Creates an unconnected SH
+          MessageHandler mh = new MessageHandler(maxQueueSize,
+              replicationServerURL, replicationServerId, replicationServer);
+          // set initial state
+          mh.setInitialServerState(newContext.startState);
+          // set serviceID and domain
+          mh.setServiceIdAndDomain(rsd.getBaseDn());
+          // register into domain
+          rsd.registerHandler(mh);
+          newContext.mh = mh;
+
+          // store the new context
+          clDomCtxts[i] = newContext;
+          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++)
+      {
+        this.getNextElligibleMessage(j);
+        if (clDomCtxts[j].nextMsg == null)
+          clDomCtxts[j].active = false;
+      }
+    }
+    catch(Exception e)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      throw new DirectoryException(
+          ResultCode.OPERATIONS_ERROR,
+          Message.raw(Category.SYNC, Severity.INFORMATION,"Exception raised: " +
+              e.getLocalizedMessage()),
+          e);
+    }
+  }
+
+  /**
+   * Registers this handler into its related domain and notifies the domain.
+   */
+  private void registerIntoDomain()
+  {
+    replicationServerDomain.registerHandler(this);
+  }
+
+  /**
+   * Shutdown this handler ServerHandler.
+   */
+  public void shutdown()
+  {
+    for (int i=0;i<clDomCtxts.length;i++)
+    {
+      if (!clDomCtxts[i].rsd.unRegisterHandler(clDomCtxts[i].mh))
+      {
+        TRACER.debugInfo(this +" shutdown() Internal error " +
+                " when unregistering "+ clDomCtxts[i].mh);
+      }
+      clDomCtxts[i].rsd.stopServer(clDomCtxts[i].mh);
+    }
+    super.shutdown();
+    clDomCtxts = null;
+  }
+
+  /**
+   * Request to shutdown the associated writer.
+   */
+  protected void shutdownWriter()
+  {
+    shutdownWriter = true;
+    if (writer!=null)
+    {
+      ECLServerWriter eclWriter = (ECLServerWriter)this.writer;
+      eclWriter.shutdownWriter();
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getMonitorInstanceName()
+  {
+    String str = serverURL + " " + String.valueOf(serverId);
+
+    return "Connected External Changelog Server " + str +
+    ",cn=" + replicationServerDomain.getMonitorInstanceName();
+  }
+
+  /**
+   * Retrieves a set of attributes containing monitor data that should be
+   * returned to the client if the corresponding monitor entry is requested.
+   *
+   * @return  A set of attributes containing monitor data that should be
+   *          returned to the client if the corresponding monitor entry is
+   *          requested.
+   */
+  @Override
+  public ArrayList<Attribute> getMonitorData()
+  {
+    // Get the generic ones
+    ArrayList<Attribute> attributes = super.getMonitorData();
+
+    // Add the specific RS ones
+    attributes.add(Attributes.create("External-Changelog-Server",
+        serverURL));
+
+    try
+    {
+      MonitorData md;
+      md = replicationServerDomain.computeMonitorData();
+      // FIXME:ECL No monitoring exist for ECL.
+    }
+    catch (Exception e)
+    {
+      Message message =
+        ERR_ERROR_RETRIEVING_MONITOR_DATA.get(stackTraceToSingleLineString(e));
+      // We failed retrieving the monitor data.
+      attributes.add(Attributes.create("error", message.toString()));
+    }
+    return attributes;
+  }
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    String localString;
+    localString = "External changelog Server ";
+    if (this.cLSearchCtxt==null)
+      localString += serverId + " " + serverURL + " " + getServiceId();
+    else
+      localString += this.getName();
+    return localString;
+  }
+  /**
+   * Gets the status of the connected DS.
+   * @return The status of the connected DS.
+   */
+  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;
+  }
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isDataServer()
+  {
+    return true;
+  }
+
+  /**
+   * Initialize the handler.
+   * @param startECLSessionMsg The provided starting state.
+   * @throws DirectoryException when a problem occurs.
+   */
+  public void initialize(StartECLSessionMsg startECLSessionMsg)
+  throws DirectoryException
+  {
+
+    //
+    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.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(
+        startECLSessionMsg.getCrossDomainServerState());
+    cLSearchCtxt.excludedServiceIDs=startECLSessionMsg.getExcludedServiceIDs();
+
+    //--
+    if (startECLSessionMsg.getECLRequestType()==0)
+    {
+      initializeCLSearchFromGenState(
+          startECLSessionMsg.getCrossDomainServerState());
+    }
+
+    if (session != null)
+    {
+      try
+      {
+        // Disable timeout for next communications
+        session.setSoTimeout(0);
+      }
+      catch(Exception e) {}
+
+      // sendWindow MUST be created before starting the writer
+      sendWindow = new Semaphore(sendWindowSize);
+
+      // create reader
+      reader = new ServerReader(session, serverId,
+          this, replicationServerDomain);
+      reader.start();
+
+      if (writer == null)
+      {
+        // create writer
+        writer = new ECLServerWriter(session,this,replicationServerDomain);
+        writer.start();
+      }
+
+      // Resume the writer
+      ((ECLServerWriter)writer).resumeWriter();
+
+      // FIXME:ECL Potential race condition if writer not yet resumed here
+    }
+
+    if (cLSearchCtxt.isPersistent == StartECLSessionMsg.PERSISTENT_CHANGES_ONLY)
+    {
+      closePhase1();
+    }
+
+    /* TODO: Good Draft Compat
+    //--
+    if (startCLMsg.getStartMode()==1)
+    {
+      initializeCLSearchFromProvidedSeqnum(startCLMsg.getSequenceNumber());
+    }
+
+    //--
+    if (startCLMsg.getStartMode()==2)
+    {
+      if (CLSearchFromProvidedExactCN(startCLMsg.getChangeNumber()))
+        return;
+    }
+
+    //--
+    if (startCLMsg.getStartMode()==4)
+    {
+      // to get the CL first and last
+      initializeCLDomCtxts(null); // from start
+      ChangeNumber crossDomainElligibleCN = computeCrossDomainElligibleCN();
+
+      try
+      {
+        // to get the CL first and last
+        // last rely on the crossDomainElligibleCN thhus must have been
+        // computed before
+        int[] limits = computeCLLimits(crossDomainElligibleCN);
+        // Send the response
+        CLLimitsMsg msg = new CLLimitsMsg(limits[0], limits[1]);
+        session.publish(msg);
+      }
+      catch(Exception e)
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        try
+        {
+          session.publish(
+            new ErrorMsg(
+             replicationServerDomain.getReplicationServer().getServerId(),
+             serverId,
+             Message.raw(Category.SYNC, Severity.INFORMATION,
+                 "Exception raised: " + e.getMessage())));
+        }
+        catch(IOException ioe)
+        {
+          // FIXME: close conn ?
+        }
+      }
+      return;
+    }
+    Good Draft Compat */
+
+    // Store into domain
+    registerIntoDomain();
+
+  }
+
+  /**
+   * Select the next update that must be sent to the server managed by this
+   * ServerHandler.
+   *
+   * @return the next update that must be sent to the server managed by this
+   *         ServerHandler.
+   * @exception DirectoryException when an error occurs.
+   */
+  public ECLUpdateMsg takeECLUpdate()
+  throws DirectoryException
+  {
+    boolean interrupted = true;
+    ECLUpdateMsg msg = getnextUpdate();
+
+    // FIXME: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
+    {
+      try
+      {
+        acquired = sendWindow.tryAcquire((long) 500, TimeUnit.MILLISECONDS);
+        interrupted = false;
+      } catch (InterruptedException e)
+      {
+        // loop until not interrupted
+      }
+    } while (((interrupted) || (!acquired)) && (!shutdownWriter));
+    if (msg != null)
+    {
+      incrementOutCount();
+    }
+    return msg;
+  }
+
+  /**
+   * Get the next message - non blocking - null when none.
+   *
+   * @param synchronous - not used - always non blocking.
+   * @return the next message - null when none.
+   */
+  protected UpdateMsg getnextMessage(boolean synchronous)
+  {
+    UpdateMsg msg = null;
+    try
+    {
+      ECLUpdateMsg eclMsg = getnextUpdate();
+      if (eclMsg!=null)
+        msg = eclMsg.getUpdateMsg();
+    }
+    catch(DirectoryException de)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, de);
+    }
+    return msg;
+  }
+
+  /**
+   * Get the next external changelog update.
+   *
+   * @return    The ECL update, null when none.
+   * @exception DirectoryException when any problem occurs.
+   */
+  protected ECLUpdateMsg getnextUpdate()
+  throws DirectoryException
+  {
+    return getGeneralizedNextECLUpdate(this.cLSearchCtxt);
+  }
+
+  /**
+   * 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
+    {
+      TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
+          getMonitorInstanceName() + "," + this +
+          " getGeneralizedNextECLUpdate starts with ctxt="
+          + cLSearchCtxt);
+
+      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;
+          }
+        }
+        */
+      }
+
+
+      // Search / no seqnum / not persistent
+      // -----------------------------------
+      //  init: all domain are candidate
+      //    get one msg from each
+      //       no (null) msg returned: should not happen since we go to a state
+      //       that is computed/expected
+      //  getMessage:
+      //    get the oldest msg:
+      //    after:
+      //     if stopState of domain is covered then domain is out candidate
+      //       until no domain candidate mean generalized stopState
+      //       has been reached
+      //     else
+      //       get one msg from that domain
+      //       no (null) msg returned: should not happen since we go to a state
+      //       that is computed/expected
+      //  step 2: send DoneMsg
+
+      // Persistent:
+      // ----------
+      //  step 1&2: same as non persistent
+      //
+      //  step 3: reinit all domain are candidate
+      //    take the oldest
+      //    if one domain has no msg, still is candidate
+
+      int iDom = 0;
+      boolean nextclchange = true;
+      while ((nextclchange) && (cLSearchCtxt.searchPhase==1))
+      {
+
+        // Step 1 & 2
+        if (cLSearchCtxt.searchPhase==1)
+        {
+          if (debugEnabled())
+            clDomCtxtsToString("In getGeneralizedNextMessage : " +
+              "looking for the generalized oldest change");
+
+          // Retrieves the index in the subx table of the
+          // generalizedOldestChange
+          iDom = getGeneralizedOldestChange();
+
+          // idomain != -1 means that we got one
+          // generalized oldest change to process
+          if (iDom==-1)
+          {
+            closePhase1();
+
+            // signify 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);
+
+          nextclchange = false;
+
+          /* TODO:ECL G Good change log draft compat.
+          if (cLSearchCtxt.nextSeqnum!=-1)
+          {
+            // Should either retrieve or generate a seqnum
+            // we also need to check if the seqnumdb is acccurate reagrding
+            // 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
+
+            // replogcn : the oldest change from the changelog db
+            ChangeNumber replogcn = theOldestChange.getChangeNumber();
+            DN replogReplDomDN = clDomCtxts[iDom].rsd.getBaseDn();
+
+            while (true)
+            {
+              if (!cLSearchCtxt.endOfSeqnumdbReached)
+              {
+                // we did not reach yet the end of the seqnumdb
+
+                // seqnumcn : the next change from from the seqnum db
+                ChangeNumber seqnumcn = seqnumDbReadIterator.getChangeNumber();
+
+                // are replogcn and seqnumcn should be the same change ?
+                int cmp = replogcn.compareTo(seqnumcn);
+                DN seqnumReplDomDN=DN.decode(seqnumDbReadIterator.
+                getDomainDN());
+
+                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 ((replogReplDomDN.compareTo(seqnumReplDomDN)==0) && (cmp==0))
+                {
+                  // same domain and same CN => same change
+
+                  // assign the seqnum from the seqnumdb
+                  // to the change from the changelogdb
+
+                  TRACER.debugInfo("seqnumgen: assigning seqnum="
+                      + seqnumDbReadIterator.getSeqnum()
+                      + " to change=" + theOldestChange);
+                  theOldestChange.setSeqnum(seqnumDbReadIterator.getSeqnum());
+
+                  // 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))
+                  {
+                    // the change from the seqnumDb is older
+                    // that means that the change has been purged from the
+                    // changelog
+                    try
+                    {
+                      // let's traverse the seqnumdb 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)
+                      {
+                        // 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++;
+                        break;
+                      }
+                      else
+                      {
+                        // next change from seqnumdb
+                        cLSearchCtxt.nextSeqnum =
+                          seqnumDbReadIterator.getSeqnum() + 1;
+                        continue;
+                      }
+                    }
+                    catch(Exception e)
+                    {
+                    }
+                  }
+                  else
+                  {
+                    // 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;
+                    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++;
+                break;
+              }
+            } // while seqnum
+          } // nextseqnum !- -1
+          */
+
+          // here we have the right oldest change and in the seqnum case we
+          // have its seqnum
+
+          // 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());
+
+          if (clDomCtxts[iDom].currentState.cover(clDomCtxts[iDom].stopState))
+          {
+            clDomCtxts[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)
+          {
+            // populates the table with the next eligible msg from idomain
+            // in non blocking mode, return null when no more eligible msg
+            getNextElligibleMessage(iDom);
+          }
+        } // phase ==1
+      } // while (nextclchange)
+
+      if (cLSearchCtxt.searchPhase==2)
+      {
+        clDomCtxtsToString("In getGeneralizedNextMessage (persistent): " +
+        "looking for the generalized oldest change");
+
+        for (int ido=0; ido<clDomCtxts.length; ido++)
+        {
+          // get next msg
+          getNextElligibleMessage(ido);
+        }
+
+        // take the oldest one
+        iDom = getGeneralizedOldestChange();
+
+        if (iDom != -1)
+        {
+          String suffix = this.clDomCtxts[iDom].rsd.getBaseDn();
+
+          theOldestChange = new ECLUpdateMsg(
+              (LDAPUpdateMsg)clDomCtxts[iDom].nextMsg,
+              null, // set later
+              suffix);
+
+          clDomCtxts[iDom].currentState.update(
+              theOldestChange.getUpdateMsg().getChangeNumber());
+
+          /* TODO:ECL G Good changelog draft compat.
+          if (cLSearchCtxt.nextSeqnum!=-1)
+          {
+            // should generate seqnum
+
+            // 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++;
+          }
+          */
+        }
+      }
+    }
+    catch(Exception e)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      throw new DirectoryException(
+          ResultCode.OPERATIONS_ERROR,
+          Message.raw(Category.SYNC, Severity.INFORMATION,"Exception raised: "),
+          e);
+    }
+
+    if (theOldestChange != null)
+    {
+      // Update the current state
+      this.cLSearchCtxt.currentCookie.update(
+          theOldestChange.getServiceId(),
+          theOldestChange.getUpdateMsg().getChangeNumber());
+
+      // Set the current value of global state in the returned message
+      theOldestChange.setCookie(this.cLSearchCtxt.currentCookie);
+    }
+    return theOldestChange;
+  }
+
+  /**
+   * Terminates the first (non persistent) phase of the search on the ECL.
+   */
+  private void closePhase1()
+  {
+    // starvation of changelog messages
+    // all domain have been unactived means are covered
+    if (debugEnabled())
+    TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
+        getMonitorInstanceName() + "," + this + " closePhase1()"
+        + " searchCtxt=" + cLSearchCtxt);
+
+    // go to persistent phase if one
+    for (int i=0; i<clDomCtxts.length; i++)
+      clDomCtxts[i].active = true;
+
+    if (this.cLSearchCtxt.isPersistent != StartECLSessionMsg.NON_PERSISTENT)
+    {
+      // phase = 1 done AND persistent search => goto phase 2
+      cLSearchCtxt.searchPhase=2;
+
+      if (writer ==null)
+      {
+        writer = new ECLServerWriter(session,this,replicationServerDomain);
+        writer.start();  // start suspended
+      }
+
+    }
+    else
+    {
+      // phase = 1 done AND !persistent search => reinit to phase 0
+      cLSearchCtxt.searchPhase=0;
+    }
+
+    /* TODO:ECL G Good changelog draft compat.
+    if (seqnumDbReadIterator!=null)
+    {
+      // End of phase 1 => always release the seqnum iterator
+      seqnumDbReadIterator.releaseCursor();
+      seqnumDbReadIterator = null;
+    }
+     */
+  }
+
+  /**
+   * Get the oldest change contained in the subx table.
+   * The subx table should be populated before
+   * @return the oldest change.
+   */
+  private int getGeneralizedOldestChange()
+  {
+    int oldest = -1;
+    for (int i=0; i<clDomCtxts.length; i++)
+    {
+      if ((clDomCtxts[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 ((oldest==-1) ||
+              (clDomCtxts[i].nextMsg.compareTo(clDomCtxts[oldest].nextMsg)<0))
+          {
+            oldest = i;
+          }
+        }
+      }
+    }
+    TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
+        getMonitorInstanceName()
+        + "," + this + " getGeneralizedOldestChange() " +
+        " returns " +
+        ((oldest!=-1)?clDomCtxts[oldest].nextMsg:""));
+
+    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);
+        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();
+      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();
+        }
+      }
+
+      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)
+      {
+        TRACER.debugInfo(
+            "For RSD." + rsd.getBaseDn() + " Server " + sid
+            + " is not considered for eligibility ... potentially down");
+        continue;
+      }
+
+      if ((elligibleCN == null) || (storedCN.older(elligibleCN)))
+      {
+        elligibleCN = storedCN;
+      }
+    }
+    TRACER.debugInfo(
+        "For RSD." + rsd.getBaseDn() + " ElligibleCN()=" + elligibleCN);
+    return elligibleCN;
+  }
+
+  /**
+   * Returns the client operation id.
+   * @return The client operation id.
+   */
+  public String getOperationId()
+  {
+    return operationId;
+  }
+
+  /**
+   * Getter for the persistent property of the current search.
+   * @return Whether the current search is persistent or not.
+   */
+  public short isPersistent() {
+    return this.cLSearchCtxt.isPersistent;
+  }
+
+  /**
+   * Getter for the current search phase (INIT or PERSISTENT).
+   * @return Whether the current search is persistent or not.
+   */
+  public int getSearchPhase() {
+    return this.cLSearchCtxt.searchPhase;
+  }
+
+}
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
new file mode 100644
index 0000000..642fa9c
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java
@@ -0,0 +1,306 @@
+/*
+ * 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.io.IOException;
+import java.net.SocketException;
+
+import org.opends.messages.Message;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.PersistentSearch;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.protocol.DoneMsg;
+import org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.replication.protocol.ProtocolSession;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.workflowelement.externalchangelog.ECLSearchOperation;
+import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
+
+
+/**
+ * This class defines a server writer, which is used to send changes to a
+ * directory server.
+ */
+public class ECLServerWriter extends ServerWriter
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  private ProtocolSession session;
+  private ECLServerHandler handler;
+  private ReplicationServerDomain replicationServerDomain;
+  private short protocolVersion = -1;
+  private boolean suspended;
+  private boolean shutdown;
+
+  /**
+   * Create a ServerWriter.
+   *
+   * @param session     the ProtocolSession that will be used to send updates.
+   * @param handler     ECL handler for which the ServerWriter is created.
+   * @param replicationServerDomain the ReplicationServerDomain of this
+   *                    ServerWriter.
+   */
+  public ECLServerWriter(ProtocolSession session, ECLServerHandler handler,
+      ReplicationServerDomain replicationServerDomain)
+  {
+    super(session, (short)-1, handler, replicationServerDomain);
+
+    setName("Replication ECL Writer Thread for op:" +
+        handler.getOperationId());
+
+    this.session = session;
+    this.handler = handler;
+    this.replicationServerDomain = replicationServerDomain;
+    // Keep protocol version locally for efficiency
+    this.protocolVersion = handler.getProtocolVersion();
+    this.suspended = false;
+    this.shutdown = false;
+  }
+
+  /**
+   * The writer will start suspended by the Handler for the CL
+   * waiting for the startCLSessionMsg. Then it may be
+   * suspended between 2 jobs, each job being a separate search.
+   */
+  public synchronized void suspendWriter()
+  {
+    synchronized(this)
+    {
+      suspended = true;
+    }
+  }
+
+  /**
+   * Resume the writer.
+   */
+  public synchronized void resumeWriter()
+  {
+    synchronized(this)
+    {
+      suspended = false;
+    }
+    notify();
+  }
+
+  /**
+   * Run method for the ServerWriter.
+   * Loops waiting for changes from the ReplicationServerDomain and
+   * forward them to the other servers
+   */
+  public void run()
+  {
+    Message errMessage = null;
+    try
+    {
+      while (true)
+      {
+        // wait to be resumed or shutdowned
+        if ((suspended) && (!shutdown))
+        {
+          synchronized(this)
+          {
+            wait();
+          }
+        }
+
+        if (shutdown)
+        {
+          return;
+        }
+
+        // Not suspended
+        doIt();
+
+
+        if (shutdown)
+        {
+          return;
+        }
+        suspendWriter();
+      }
+    }
+    catch (SocketException e)
+    {
+      // Just ignore the exception and let the thread die as well
+      errMessage = NOTE_SERVER_DISCONNECT.get(handler.toString(),
+          "for operation " + handler.getOperationId());
+      logError(errMessage);
+    }
+    catch (Exception e)
+    {
+      // An unexpected error happened.
+      // Log an error and close the connection.
+      errMessage = ERR_WRITER_UNEXPECTED_EXCEPTION.get(handler.toString() +
+          " " +  stackTraceToSingleLineString(e));
+      logError(errMessage);
+    }
+    finally
+    {
+      if (session!=null)
+      {
+        try
+        {
+          session.close();
+        } catch (IOException e)
+        {
+          // Can't do much more : ignore
+        }
+      }
+      replicationServerDomain.stopServer(handler);
+    }
+  }
+
+  /**
+   * Loop gettting 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
+   */
+  public void doIt()
+  throws IOException, InterruptedException
+  {
+    ECLUpdateMsg update = null;
+    while (true)
+    {
+      if (shutdown)
+      {
+        return;
+      }
+
+      if (suspended)
+      {
+        return;
+      }
+
+      // TODO:ECL please document the details of the cases where update could
+      // be not null here
+      if (update == null)
+      {
+        try
+        {
+          update = handler.takeECLUpdate();
+        }
+        catch(DirectoryException de)
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+      }
+
+      if (update == null)
+      {
+        if (handler.getSearchPhase() != 1)
+        {
+          if (session!=null)
+          {
+            // session is null in pusherOnly mode
+            // Done is used to end phase 1
+            session.publish(new DoneMsg(
+                replicationServerDomain.getReplicationServer().getServerId(),
+                handler.getServerId()), protocolVersion);
+          }
+        }
+
+        if (handler.isPersistent() == StartECLSessionMsg.NON_PERSISTENT)
+        {
+          // publishing is normally stopped here
+          break;
+        }
+        else
+        {
+          // except if we are in persistent search
+          Thread.sleep(200);
+          continue;
+        }
+      }
+      else
+      {
+        // Publish the update to the remote server using a protocol version he
+        // it supports
+        publish(update, protocolVersion);
+        update = null;
+      }
+    }
+}
+
+  /**
+   * Shutdown the writer.
+   */
+  public synchronized void shutdownWriter()
+  {
+    shutdown = true;
+    this.notify();
+  }
+
+  /**
+   * Publish a change either on the protocol session or to a persistent search.
+   */
+  private void publish(ECLUpdateMsg msg, short reqProtocolVersion)
+  throws IOException
+  {
+    if (debugEnabled())
+      TRACER.debugInfo(this + " publishes msg=[" + msg.toString() + "]");
+
+    if (session!=null)
+    {
+      session.publish(msg, protocolVersion);
+    }
+    else
+    {
+      ECLWorkflowElement wfe = (ECLWorkflowElement)
+      DirectoryServer.getWorkflowElement(
+          ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
+
+      // Notify persistent searches.
+      for (PersistentSearch psearch : wfe.getPersistentSearches())
+      {
+        try
+        {
+          Entry eclEntry = ECLSearchOperation.createEntryFromMsg(msg);
+          psearch.processAdd(eclEntry, -1);
+        }
+        catch(Exception e)
+        {
+          Message errMessage =
+            ERR_WRITER_UNEXPECTED_EXCEPTION.get(handler.toString() +
+              " " +  stackTraceToSingleLineString(e));
+          logError(errMessage);
+        }
+      }
+    }
+  }
+}
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
new file mode 100644
index 0000000..2bf4bf1
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java
@@ -0,0 +1,85 @@
+/*
+ * 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 org.opends.server.replication.common.ExternalChangeLogSession;
+import org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
+import org.opends.server.types.DirectoryException;
+
+/**
+ * This interface defines a session used to search the external changelog
+ * in the Directory Server.
+ */
+public class ExternalChangeLogSessionImpl
+  implements ExternalChangeLogSession
+{
+
+  ECLServerHandler handler;
+  ReplicationServer rs;
+
+  /**
+   * Create a new external changelog session.
+   * @param rs The replication server to which we will request the log.
+   * @param startECLSessionMsg The start session message containing the
+   *        details of the search request on the ECL.
+   * @throws DirectoryException When an error occurs.
+   */
+  public ExternalChangeLogSessionImpl(
+      ReplicationServer rs,
+      StartECLSessionMsg startECLSessionMsg)
+  throws DirectoryException
+  {
+    this.rs = rs;
+
+    this.handler = new ECLServerHandler(
+        rs.getServerURL(),
+        rs.getServerId(),
+        rs,
+        startECLSessionMsg);
+  }
+
+  /**
+   * Returns the next message available for the ECL (blocking)
+   * null when none.
+   * @return the next available message from the ECL.
+   * @throws DirectoryException when needed.
+   */
+  public ECLUpdateMsg getNextUpdate()
+  throws DirectoryException
+  {
+    return handler.getnextUpdate();
+  }
+
+  /**
+   * Close the session.
+   */
+  public void close()
+  {
+    handler.getDomain().stopServer(handler);
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java
index b4634fc..eae7115 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java
@@ -64,7 +64,7 @@
   // The tracer object for the debug logger.
   private static final DebugTracer TRACER = getTracer();
 
-  private ServerHandler replServerHandler;
+  private ReplicationServerHandler replServerHandler;
   private ReplicationServerDomain rsDomain;
   // The id of the RS this DS is connected to
   private short replicationServerId = -1;
@@ -103,7 +103,7 @@
    * @param assuredMode The assured mode of the remote DS
    * @param safeDataLevel The safe data level of the remote DS
    */
-  public LightweightServerHandler(ServerHandler replServerHandler,
+  public LightweightServerHandler(ReplicationServerHandler replServerHandler,
     short replicationServerId, short serverId, long generationId, byte groupId,
     ServerStatus status, List<String> refUrls, boolean assuredFlag,
     AssuredMode assuredMode, byte safeDataLevel)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/MessageHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/MessageHandler.java
new file mode 100644
index 0000000..cadb898
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/MessageHandler.java
@@ -0,0 +1,912 @@
+/*
+ * 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.ERR_RS_DN_DOES_NOT_MATCH;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+
+import java.util.ArrayList;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.std.server.MonitorProviderCfg;
+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.protocol.UpdateMsg;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.util.TimeThread;
+
+/**
+ * This class implements a buffering/producer/consumer mechanism of
+ * replication changes (UpdateMsg) used inside the replication server.
+ *
+ * MessageHandlers are registered into Replication server domains.
+ * When an update message is received by a domain, the domain forwards
+ * the message to the registered message handlers.
+ * Message are buffered into a queue.
+ * Consumers are expected to come and consume the UpdateMsg from the queue.
+ */
+public class MessageHandler extends MonitorProvider<MonitorProviderCfg>
+{
+
+  /**
+   * The tracer object for the debug logger.
+   */
+  protected static final DebugTracer TRACER = getTracer();
+  /**
+   * UpdateMsg queue.
+   */
+  private final MsgQueue msgQueue = new MsgQueue();
+  /**
+   * Late queue.
+   */
+  protected MsgQueue lateQueue = new MsgQueue();
+  /**
+   * Local hosting RS.
+   */
+  protected ReplicationServer replicationServer = null;
+  /**
+   * The URL of the hosting replication server.
+   */
+  protected String replicationServerURL = null;
+  /**
+   * The serverID of the hosting replication server.
+   */
+  protected short replicationServerId;
+  /**
+   * Specifies the related replication server domain based on serviceId(baseDn).
+   */
+  protected ReplicationServerDomain replicationServerDomain = null;
+  /**
+   * Number of update sent to the server.
+   */
+  protected int outCount = 0;
+  /**
+   * Number of updates received from the server.
+   */
+  protected int inCount = 0;
+  /**
+   * Specifies the max receive queue for this handler.
+   */
+  protected int maxReceiveQueue = 0;
+  /**
+   * Specifies the max send queue for this handler.
+   */
+  protected int maxSendQueue = 0;
+  /**
+   * Specifies the max receive delay for this handler.
+   */
+  protected int maxReceiveDelay = 0;
+  /**
+   * Specifies the max send delay for this handler.
+   */
+  protected int maxSendDelay = 0;
+  /**
+   * Specifies the max queue size for this handler.
+   */
+  protected int maxQueueSize = 5000;
+  /**
+   * Specifies the max queue size in bytes for this handler.
+   */
+  protected int maxQueueBytesSize = maxQueueSize * 100;
+  /**
+   * Specifies the max restart receive queue for this handler.
+   */
+  protected int restartReceiveQueue;
+  /**
+   * Specifies the max restart send queue for this handler.
+   */
+  protected int restartSendQueue;
+  /**
+   * Specifies the max restart receive delay for this handler.
+   */
+  protected int restartReceiveDelay;
+  /**
+   * Specifies the max restart send delay for this handler.
+   */
+  protected int restartSendDelay;
+  /**
+   * Specifies whether the consumer is following the producer (is not late).
+   */
+  protected boolean following = false;
+  /**
+   * Specifies the current serverState of this handler.
+   */
+  private ServerState serverState;
+  /**
+   * Specifies the identifier of the service (usually the baseDn of the domain).
+   */
+  private String serviceId = null;
+  /**
+   * Specifies whether the server is flow controlled and should be stopped from
+   * sending messages.
+   */
+  protected boolean flowControl = false;
+  /**
+   * Specifies whether the consumer is still active or not.
+   * If not active, the handler will not return any message.
+   * Called at the beginning of shutdown process.
+   */
+  private boolean activeConsumer = true;
+  /**
+   * Set when ServerHandler is stopping.
+   */
+  private AtomicBoolean shuttingDown = new AtomicBoolean(false);
+
+  /**
+   * Creates a new server handler instance with the provided socket.
+   * @param queueSize The maximum number of update that will be kept
+   *                  in memory by this ServerHandler.
+   * @param replicationServerURL The URL of the hosting replication server.
+   * @param replicationServerId The ID of the hosting replication server.
+   * @param replicationServer The hosting replication server.
+   */
+  public MessageHandler(
+      int queueSize,
+      String replicationServerURL,
+      short replicationServerId,
+      ReplicationServer replicationServer)
+  {
+    super("Message Handler");
+    this.maxQueueSize = queueSize;
+    this.maxQueueBytesSize = queueSize * 100;
+    this.replicationServerURL = replicationServerURL;
+    this.replicationServerId = replicationServerId;
+    this.replicationServer = replicationServer;
+  }
+
+  /**
+   * Add an update to the list of updates that must be sent to the server
+   * managed by this Handler.
+   *
+   * @param update The update that must be added to the list of updates of
+   * this handler.
+   * @param sourceHandler The source handler that generated the update.
+   */
+  public void add(UpdateMsg update, MessageHandler sourceHandler)
+  {
+    synchronized (msgQueue)
+    {
+      /*
+       * If queue was empty the writer thread was probably asleep
+       * waiting for some changes, wake it up
+       */
+      if (msgQueue.isEmpty())
+        msgQueue.notify();
+
+      msgQueue.add(update);
+
+      /* TODO : size should be configurable
+       * and larger than max-receive-queue-size
+       */
+      while ((msgQueue.count() > maxQueueSize) ||
+          (msgQueue.bytesCount() > maxQueueBytesSize))
+      {
+        setFollowing(false);
+        msgQueue.removeFirst();
+      }
+    }
+
+    if (isSaturated(update.getChangeNumber(), sourceHandler))
+    {
+      sourceHandler.setSaturated(true);
+    }
+
+  }
+  /**
+   * Set the shut down flag to true and returns the previous value of the flag.
+   * @return The previous value of the shut down flag
+   */
+  public boolean engageShutdown()
+  {
+    // Use thread safe boolean
+    return shuttingDown.getAndSet(true);
+  }
+
+  /**
+   * Get an approximation of the delay by looking at the age of the oldest
+   * message that has not been sent to this server.
+   * This is an approximation because the age is calculated using the
+   * clock of the server where the replicationServer is currently running
+   * while it should be calculated using the clock of the server
+   * that originally processed the change.
+   *
+   * The approximation error is therefore the time difference between
+   *
+   * @return the approximate delay for the connected server.
+   */
+  public long getApproxDelay()
+  {
+    long olderUpdateTime = getOlderUpdateTime();
+    if (olderUpdateTime == 0)
+      return 0;
+
+    long currentTime = TimeThread.getTime();
+    return ((currentTime - olderUpdateTime) / 1000);
+  }
+
+  /**
+   * Get the age of the older change that has not yet been replicated
+   * to the server handled by this ServerHandler.
+   * @return The age if the older change has not yet been replicated
+   *         to the server handled by this ServerHandler.
+   */
+  public Long getApproxFirstMissingDate()
+  {
+    Long result = (long) 0;
+
+    // Get the older CN received
+    ChangeNumber olderUpdateCN = getOlderUpdateCN();
+    if (olderUpdateCN != null)
+    {
+      // If not present in the local RS db,
+      // then approximate with the older update time
+      result = olderUpdateCN.getTime();
+    }
+    return result;
+  }
+
+  /**
+   * Returns the Replication Server Domain to which belongs this handler.
+   * @param createIfNotExist Creates the domain if it does not exist.
+   * @return The replication server domain.
+   */
+  public ReplicationServerDomain getDomain(boolean createIfNotExist)
+  {
+    if (replicationServerDomain==null)
+    {
+      replicationServerDomain =
+      replicationServer.getReplicationServerDomain(serviceId,createIfNotExist);
+    }
+    return replicationServerDomain;
+  }
+
+  /**
+   * Get the count of updates received from the server.
+   * @return the count of update received from the server.
+   */
+  public int getInCount()
+  {
+    return inCount;
+  }
+
+  /**
+   * Retrieves a set of attributes containing monitor data that should be
+   * returned to the client if the corresponding monitor entry is requested.
+   *
+   * @return  A set of attributes containing monitor data that should be
+   *          returned to the client if the corresponding monitor entry is
+   *          requested.
+   */
+  @Override
+  public ArrayList<Attribute> getMonitorData()
+  {
+    ArrayList<Attribute> attributes = new ArrayList<Attribute>();
+    attributes.add(Attributes.create("handler", getMonitorInstanceName()));
+    attributes.add(
+        Attributes.create("queue-size", String.valueOf(msgQueue.count())));
+    attributes.add(
+        Attributes.create(
+            "queue-size-bytes", String.valueOf(msgQueue.bytesCount())));
+    attributes.add(
+        Attributes.create(
+            "following", String.valueOf(following)));
+    return attributes;
+  }
+
+  /**
+   * Retrieves the name of this monitor provider.  It should be unique among all
+   * monitor providers, including all instances of the same monitor provider.
+   *
+   * @return  The name of this monitor provider.
+   */
+  @Override
+  public String getMonitorInstanceName()
+  {
+    return "Message Handler";
+  }
+
+  /**
+   * Get the next update that must be sent to the consumer
+   * from the message queue or from the database.
+   *
+   * @param  synchronous specifies what to do when the queue is empty.
+   *         when true, the method blocks; when false the method return null.
+   * @return The next update that must be sent to the consumer.
+   *         null when synchronous is false and queue is empty.
+   */
+  protected UpdateMsg getnextMessage(boolean synchronous)
+  {
+    UpdateMsg msg;
+    while (activeConsumer == true)
+    {
+      if (following == false)
+      {
+        /* this server is late with regard to some other masters
+         * in the topology or just joined the topology.
+         * In such cases, we can't keep all changes in the queue
+         * without saturating the memory, we therefore use
+         * a lateQueue that is filled with a few changes from the changelogDB
+         * If this server is able to close the gap, it will start using again
+         * the regular msgQueue later.
+         */
+        if (lateQueue.isEmpty())
+        {
+          /*
+           * Start from the server State
+           * Loop until the queue high mark or until no more changes
+           *   for each known LDAP master
+           *      get the next CSN after this last one :
+           *         - try to get next from the file
+           *         - if not found in the file
+           *             - try to get the next from the queue
+           *   select the smallest of changes
+           *   check if it is in the memory tree
+           *     yes : lock memory tree.
+           *           check all changes from the list, remove the ones that
+           *           are already sent
+           *           unlock memory tree
+           *           restart as usual
+           *   load this change on the delayList
+           *
+           */
+          ReplicationIteratorComparator comparator =
+            new ReplicationIteratorComparator();
+          SortedSet<ReplicationIterator> iteratorSortedSet =
+            new TreeSet<ReplicationIterator>(comparator);
+          /* fill the lateQueue */
+          for (short serverId : replicationServerDomain.getServers())
+          {
+            ChangeNumber lastCsn = serverState.getMaxChangeNumber(serverId);
+            ReplicationIterator iterator =
+              replicationServerDomain.getChangelogIterator(serverId, lastCsn);
+            if (iterator != null)
+            {
+              if (iterator.getChange() != null)
+              {
+                iteratorSortedSet.add(iterator);
+              } else
+              {
+                iterator.releaseCursor();
+              }
+            }
+          }
+
+          // The loop below relies on the fact that it is sorted based
+          // on the currentChange of each iterator to consider the next
+          // change across all servers.
+          // Hence it is necessary to remove and eventual add again an iterator
+          // when looping in order to keep consistent the order of the
+          // iterators (see ReplicationIteratorComparator.
+          while (!iteratorSortedSet.isEmpty() &&
+              (lateQueue.count()<100) &&
+              (lateQueue.bytesCount()<50000) )
+          {
+            ReplicationIterator iterator = iteratorSortedSet.first();
+            iteratorSortedSet.remove(iterator);
+            lateQueue.add(iterator.getChange());
+            if (iterator.next())
+              iteratorSortedSet.add(iterator);
+            else
+              iterator.releaseCursor();
+          }
+          for (ReplicationIterator iterator : iteratorSortedSet)
+          {
+            iterator.releaseCursor();
+          }
+          /*
+           * Check if the first change in the lateQueue is also on the regular
+           * queue
+           */
+          if (lateQueue.isEmpty())
+          {
+            synchronized (msgQueue)
+            {
+              if ((msgQueue.count() < maxQueueSize) &&
+                  (msgQueue.bytesCount() < maxQueueBytesSize))
+              {
+                setFollowing(true);
+              }
+            }
+          } else
+          {
+            msg = lateQueue.first();
+            synchronized (msgQueue)
+            {
+              if (msgQueue.contains(msg))
+              {
+                /* we finally catch up with the regular queue */
+                setFollowing(true);
+                lateQueue.clear();
+                UpdateMsg msg1;
+                do
+                {
+                  msg1 = msgQueue.removeFirst();
+                } while (!msg.getChangeNumber().equals(msg1.getChangeNumber()));
+                this.updateServerState(msg);
+                return msg;
+              }
+            }
+          }
+        } else
+        {
+          /* get the next change from the lateQueue */
+          msg = lateQueue.removeFirst();
+          this.updateServerState(msg);
+          return msg;
+        }
+      }
+      synchronized (msgQueue)
+      {
+        if (following == true)
+        {
+          try
+          {
+            while (msgQueue.isEmpty() && (following == true))
+            {
+              if (!synchronous)
+                return null;
+              msgQueue.wait(500);
+              if (!activeConsumer)
+                return null;
+            }
+          } catch (InterruptedException e)
+          {
+            return null;
+          }
+          msg = msgQueue.removeFirst();
+
+          if (this.updateServerState(msg))
+          {
+            /*
+             * Only push the message if it has not yet been seen
+             * by the other server.
+             * Otherwise just loop to select the next message.
+             */
+            return msg;
+          }
+        }
+      }
+      /*
+       * Need to loop because following flag may have gone to false between
+       * the first check at the beginning of this method
+       * and the second check just above.
+       */
+    }
+    return null;
+  }
+
+  /**
+   * Get the older Change Number for that server.
+   * Returns null when the queue is empty.
+   * @return The older change number.
+   */
+  public ChangeNumber getOlderUpdateCN()
+  {
+    ChangeNumber result = null;
+    synchronized (msgQueue)
+    {
+      if (isFollowing())
+      {
+        if (msgQueue.isEmpty())
+        {
+          result = null;
+        } else
+        {
+          UpdateMsg msg = msgQueue.first();
+          result = msg.getChangeNumber();
+        }
+      } else
+      {
+        if (lateQueue.isEmpty())
+        {
+          // isFollowing is false AND lateQueue is empty
+          // We may be at the very moment when the writer has emptyed the
+          // lateQueue when it sent the last update. The writer will fill again
+          // the lateQueue when it will send the next update but we are not yet
+          // there. So let's take the last change not sent directly from
+          // the db.
+
+          ReplicationIteratorComparator comparator =
+            new ReplicationIteratorComparator();
+          SortedSet<ReplicationIterator> iteratorSortedSet =
+            new TreeSet<ReplicationIterator>(comparator);
+          try
+          {
+            // Build a list of candidates iterator (i.e. db i.e. server)
+            for (short serverId : replicationServerDomain.getServers())
+            {
+              // get the last already sent CN from that server
+              ChangeNumber lastCsn = serverState.getMaxChangeNumber(serverId);
+              // get an iterator in this server db from that last change
+              ReplicationIterator iterator =
+                replicationServerDomain.getChangelogIterator(serverId, lastCsn);
+              // if that iterator has changes, then it is a candidate
+              // it is added in the sorted list at a position given by its
+              // current change (see ReplicationIteratorComparator).
+              if ((iterator != null) && (iterator.getChange() != null))
+              {
+                iteratorSortedSet.add(iterator);
+              }
+            }
+            UpdateMsg msg = iteratorSortedSet.first().getChange();
+            result = msg.getChangeNumber();
+          } catch (Exception e)
+          {
+            result = null;
+          } finally
+          {
+            for (ReplicationIterator iterator : iteratorSortedSet)
+            {
+              iterator.releaseCursor();
+            }
+          }
+        } else
+        {
+          UpdateMsg msg = lateQueue.first();
+          result = msg.getChangeNumber();
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Get the older update time for that server.
+   * @return The older update time.
+   */
+  public long getOlderUpdateTime()
+  {
+    ChangeNumber olderUpdateCN = getOlderUpdateCN();
+    if (olderUpdateCN == null)
+      return 0;
+    return olderUpdateCN.getTime();
+  }
+
+  /**
+   * Get the count of updates sent to this server.
+   * @return  The count of update sent to this server.
+   */
+  public int getOutCount()
+  {
+    return outCount;
+  }
+
+  /**
+   * Get the number of message in the receive message queue.
+   * @return Size of the receive message queue.
+   */
+  public int getRcvMsgQueueSize()
+  {
+    synchronized (msgQueue)
+    {
+      /*
+       * When the server is up to date or close to be up to date,
+       * the number of updates to be sent is the size of the receive queue.
+       */
+      if (isFollowing())
+        return msgQueue.count();
+      else
+      {
+        /**
+         * When the server  is not able to follow, the msgQueue
+         * may become too large and therefore won't contain all the
+         * changes. Some changes may only be stored in the backing DB
+         * of the servers.
+         * The total size of the receive queue is calculated by doing
+         * the sum of the number of missing changes for every dbHandler.
+         */
+        ServerState dbState = replicationServerDomain.getDbServerState();
+        return ServerState.diffChanges(dbState, serverState);
+      }
+    }
+  }
+
+  /**
+   * Get the state of this server.
+   *
+   * @return ServerState the state for this server..
+   */
+  public ServerState getServerState()
+  {
+    return serverState;
+  }
+
+  /**
+   * Get the name of the serviceId (usually baseDn) for this handler.
+   * @return The name of the serviceId.
+   */
+  protected String getServiceId()
+  {
+    return serviceId;
+  }
+
+  /**
+   * Retrieves the length of time in milliseconds that should elapse between
+   * calls to the <CODE>updateMonitorData()</CODE> method.  A negative or zero
+   * return value indicates that the <CODE>updateMonitorData()</CODE> method
+   * should not be periodically invoked.
+   *
+   * @return  The length of time in milliseconds that should elapse between
+   *          calls to the <CODE>updateMonitorData()</CODE> method.
+   */
+  @Override
+  public long getUpdateInterval()
+  {
+    /* we don't wont to do polling on this monitor */
+    return 0;
+  }
+
+  /**
+   * Increase the counter of update received from the server.
+   */
+  public void incrementInCount()
+  {
+    inCount++;
+  }
+
+  /**
+   * Increase the counter of updates sent to the server.
+   */
+  public void incrementOutCount()
+  {
+    outCount++;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void initializeMonitorProvider(MonitorProviderCfg configuration)
+  throws ConfigException, InitializationException
+  {
+    // Nothing to do for now
+  }
+
+  /**
+   * Check if the LDAP server can follow the speed of the other servers.
+   * @return true when the server has all the not yet sent changes
+   *         in its queue.
+   */
+  public boolean isFollowing()
+  {
+    return following;
+  }
+
+  /**
+   * Check is this server is saturated (this server has already been
+   * sent a bunch of updates and has not processed them so they are staying
+   * in the message queue for this server an the size of the queue
+   * for this server is above the configured limit.
+   *
+   * The limit can be defined in number of updates or with a maximum delay
+   *
+   * @param changeNumber The changenumber to use to make the delay calculations.
+   * @param sourceHandler The ServerHandler which is sending the update.
+   * @return true is saturated false if not saturated.
+   */
+  public boolean isSaturated(ChangeNumber changeNumber,
+      MessageHandler sourceHandler)
+  {
+    synchronized (msgQueue)
+    {
+      int size = msgQueue.count();
+
+      if ((maxReceiveQueue > 0) && (size >= maxReceiveQueue))
+        return true;
+
+      if ((sourceHandler.maxSendQueue > 0) &&
+          (size >= sourceHandler.maxSendQueue))
+        return true;
+
+      if (!msgQueue.isEmpty())
+      {
+        UpdateMsg firstUpdate = msgQueue.first();
+
+        if (firstUpdate != null)
+        {
+          long timeDiff = changeNumber.getTimeSec() -
+          firstUpdate.getChangeNumber().getTimeSec();
+
+          if ((maxReceiveDelay > 0) && (timeDiff >= maxReceiveDelay))
+            return true;
+
+          if ((sourceHandler.maxSendDelay > 0) &&
+              (timeDiff >= sourceHandler.maxSendDelay))
+            return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  /**
+   * Check that the size of the Server Handler messages Queue has lowered
+   * below the limit and therefore allowing the reception of messages
+   * from other servers to restart.
+   * @param source The ServerHandler which was sending the update.
+   *        can be null.
+   * @return true if the processing can restart
+   */
+  public boolean restartAfterSaturation(MessageHandler source)
+  {
+    synchronized (msgQueue)
+    {
+      int queueSize = msgQueue.count();
+      if ((maxReceiveQueue > 0) && (queueSize >= restartReceiveQueue))
+        return false;
+      if ((source != null) && (source.maxSendQueue > 0) &&
+          (queueSize >= source.restartSendQueue))
+        return false;
+
+      if (!msgQueue.isEmpty())
+      {
+        UpdateMsg firstUpdate = msgQueue.first();
+        UpdateMsg lastUpdate = msgQueue.last();
+
+        if ((firstUpdate != null) && (lastUpdate != null))
+        {
+          long timeDiff = lastUpdate.getChangeNumber().getTimeSec() -
+          firstUpdate.getChangeNumber().getTimeSec();
+          if ((maxReceiveDelay > 0) && (timeDiff >= restartReceiveDelay))
+            return false;
+          if ((source != null) && (source.maxSendDelay > 0) && (timeDiff >=
+            source.restartSendDelay))
+            return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Set that the consumer is now becoming inactive and thus getNextMessage
+   * should not return any UpdateMsg any more.
+   * @param active the provided state of the consumer.
+   */
+  public void setConsumerActive(boolean active)
+  {
+    this.activeConsumer = active;
+  }
+
+  /**
+   * Set the following flag of this server.
+   * @param following the value that should be set.
+   */
+  private void setFollowing(boolean following)
+  {
+    this.following = following;
+  }
+
+  private void setSaturated(boolean value)
+  {
+    flowControl = value;
+  }
+
+  /**
+   * Set the initial value of the serverState for this handler.
+   * Expected to be done once, then the state will be updated using
+   * updateServerState() method.
+   * @param serverState the provided serverState.
+   * @exception DirectoryException raised when a problem occurs.
+   */
+  public void setInitialServerState(ServerState serverState)
+  throws DirectoryException
+  {
+    this.serverState = serverState;
+  }
+
+
+  /**
+   * Set the serviceId (usually baseDn) for this handler. Expected to be done
+   * once and never changed during the handler life.
+   * @param serviceId The provided serviceId.
+   * @exception DirectoryException raised when a problem occurs.
+   */
+  protected void setServiceIdAndDomain(String serviceId)
+  throws DirectoryException
+  {
+    if (this.serviceId != null)
+    {
+      if (!this.serviceId.equalsIgnoreCase(serviceId))
+      {
+        Message message = ERR_RS_DN_DOES_NOT_MATCH.get(
+            this.serviceId.toString(),
+            serviceId.toString());
+        throw new DirectoryException(ResultCode.OTHER,
+            message, null);
+      }
+    }
+    else
+    {
+      this.serviceId = serviceId;
+      this.replicationServerDomain = getDomain(true);
+    }
+  }
+
+  /**
+   * Shutdown this handler.
+   */
+  public void shutdown()
+  {
+    /*
+     * Shutdown ServerWriter
+     */
+    synchronized (msgQueue)
+    {
+      msgQueue.clear();
+      msgQueue.notify();
+      msgQueue.notifyAll();
+    }
+
+    DirectoryServer.deregisterMonitorProvider(getMonitorInstanceName());
+  }
+
+  /**
+   * Performs any processing periodic processing that may be desired to update
+   * the information associated with this monitor.  Note that best-effort
+   * attempts will be made to ensure that calls to this method come
+   * <CODE>getUpdateInterval()</CODE> milliseconds apart, but no guarantees will
+   * be made.
+   */
+  @Override
+  public void updateMonitorData()
+  {
+    // As long as getUpdateInterval() returns 0, this will never get called
+  }
+
+  /**
+   * Update the serverState with the last message sent.
+   *
+   * @param msg the last update sent.
+   * @return boolean indicating if the update was meaningful.
+   */
+  public boolean updateServerState(UpdateMsg msg)
+  {
+    return serverState.update(msg.getChangeNumber());
+  }
+
+  /**
+   * Get the groupId of the hosting RS.
+   * @return the group id.
+   */
+  public byte getLocalGroupId()
+  {
+    return replicationServer.getGroupId();
+  }
+
+}
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 348680a..8c21f77 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,16 +42,18 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
+import org.opends.messages.Category;
 import org.opends.messages.Message;
 import org.opends.messages.MessageBuilder;
+import org.opends.messages.Severity;
 import org.opends.server.admin.server.ConfigurationChangeListener;
 import org.opends.server.admin.std.server.ReplicationServerCfg;
 import org.opends.server.api.Backend;
@@ -61,13 +63,22 @@
 import org.opends.server.api.RestoreTaskListener;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.WorkflowImpl;
+import org.opends.server.core.networkgroups.NetworkGroup;
 import org.opends.server.loggers.LogLevel;
 import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.ExternalChangeLogSession;
 import org.opends.server.replication.protocol.ProtocolSession;
+import org.opends.server.replication.protocol.ReplServerStartMsg;
 import org.opends.server.replication.protocol.ReplSessionSecurity;
+import org.opends.server.replication.protocol.ReplicationMsg;
+import org.opends.server.replication.protocol.ServerStartECLMsg;
+import org.opends.server.replication.protocol.ServerStartMsg;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
 import org.opends.server.types.BackupConfig;
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.Entry;
 import org.opends.server.types.LDIFExportConfig;
@@ -75,7 +86,9 @@
 import org.opends.server.types.RestoreConfig;
 import org.opends.server.types.ResultCode;
 import org.opends.server.util.LDIFReader;
+import org.opends.server.util.ServerConstants;
 import org.opends.server.util.TimeThread;
+import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
 
 import com.sleepycat.je.DatabaseException;
 
@@ -155,6 +168,8 @@
    */
   private static final DebugTracer TRACER = getTracer();
 
+  private String externalChangeLogWorkflowID = "External Changelog Workflow ID";
+  ECLWorkflowElement eclwe;
   private static HashSet<Integer> localPorts = new HashSet<Integer>();
 
   /**
@@ -267,9 +282,35 @@
              ReplSessionSecurity.HANDSHAKE_TIMEOUT);
         if (session == null) // Error, go back to accept
           continue;
-        ServerHandler handler = new ServerHandler(session, queueSize);
-        handler.start(null, serverId, serverURL, rcvWindow,
-                      false, this);
+
+        ReplicationMsg msg = session.receive();
+
+        if (msg instanceof ServerStartMsg)
+        {
+          DataServerHandler handler = new DataServerHandler(session,
+              queueSize,serverURL,serverId,this,rcvWindow);
+          handler.startFromRemoteDS((ServerStartMsg)msg);
+        }
+        else if (msg instanceof ReplServerStartMsg)
+        {
+          ReplicationServerHandler handler = new ReplicationServerHandler(
+              session,queueSize,serverURL,serverId,this,rcvWindow);
+          handler.startFromRemoteRS((ReplServerStartMsg)msg);
+        }
+        else if (msg instanceof ServerStartECLMsg)
+        {
+          ECLServerHandler handler = new ECLServerHandler(
+              session,queueSize,serverURL,serverId,this,rcvWindow);
+          handler.startFromRemoteServer((ServerStartECLMsg)msg);
+        }
+        else
+        {
+          // We did not recognize the message, close session as what
+          // can happen after is undetermined and we do not want the server to
+          // be disturbed
+          ServerHandler.closeSession(session, null, null);
+          return;
+        }
       }
       catch (Exception e)
       {
@@ -277,6 +318,10 @@
         // shutdown or changing the port number process.
         // Just log debug information and loop.
         // Do not log the message during shutdown.
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
         if (shutdown == false) {
           Message message =
             ERR_EXCEPTION_LISTENING.get(e.getLocalizedMessage());
@@ -379,20 +424,20 @@
   /**
    * Establish a connection to the server with the address and port.
    *
-   * @param serverURL  The address and port for the server, separated by a
+   * @param remoteServerURL  The address and port for the server, separated by a
    *                    colon.
    * @param baseDn     The baseDn of the connection
    */
-  private void connect(String serverURL, String baseDn)
+  private void connect(String remoteServerURL, String baseDn)
   {
-    int separator = serverURL.lastIndexOf(':');
-    String port = serverURL.substring(separator + 1);
-    String hostname = serverURL.substring(0, separator);
-    boolean sslEncryption = replSessionSecurity.isSslEncryption(serverURL);
+    int separator = remoteServerURL.lastIndexOf(':');
+    String port = remoteServerURL.substring(separator + 1);
+    String hostname = remoteServerURL.substring(0, separator);
+    boolean sslEncryption =replSessionSecurity.isSslEncryption(remoteServerURL);
 
     if (debugEnabled())
       TRACER.debugInfo("RS " + this.getMonitorInstanceName() +
-               " connects to " + serverURL);
+               " connects to " + remoteServerURL);
 
     try
     {
@@ -402,12 +447,25 @@
       socket.setTcpNoDelay(true);
       socket.connect(ServerAddr, 500);
 
+      /*
       ServerHandler handler = new ServerHandler(
            replSessionSecurity.createClientSession(serverURL, socket,
            ReplSessionSecurity.HANDSHAKE_TIMEOUT),
            queueSize);
       handler.start(baseDn, serverId, this.serverURL, rcvWindow,
                     sslEncryption, this);
+      */
+
+      ReplicationServerHandler handler = new ReplicationServerHandler(
+          replSessionSecurity.createClientSession(remoteServerURL,
+              socket,
+              ReplSessionSecurity.HANDSHAKE_TIMEOUT),
+              queueSize,
+              this.serverURL,
+              serverId,
+              this,
+              rcvWindow);
+      handler.connect(baseDn, sslEncryption);
     }
     catch (Exception e)
     {
@@ -470,6 +528,10 @@
         serverId , this);
       listenThread.start();
 
+      // Initialize the External Changelog
+      // FIXME: how is WF creation enabed/disabled in the RS ?
+      initializeECL();
+
       if (debugEnabled())
         TRACER.debugInfo("RS " +getMonitorInstanceName()+
             " successfully initialized");
@@ -493,10 +555,88 @@
       Message message =
           ERR_COULD_NOT_BIND_CHANGELOG.get(changelogPort, e.getMessage());
       logError(message);
+    } catch (DirectoryException e)
+    {
+      //FIXME:DirectoryException is raised by initializeECL => fix err msg
+      Message message = Message.raw(Category.SYNC, Severity.SEVERE_ERROR,
+        "Directory Exception raised by ECL initialization: " + e.getMessage());
+      logError(message);
     }
   }
 
   /**
+   * Initializes the ECL access by creating a dedicated workflow element.
+   * @throws DirectoryException
+   */
+  private void initializeECL()
+  throws DirectoryException
+  {
+    WorkflowImpl externalChangeLogWorkflow;
+    if (WorkflowImpl.getWorkflow(externalChangeLogWorkflowID)
+        !=null)
+      return;
+
+    ECLWorkflowElement eclwe = new ECLWorkflowElement(this);
+
+    // Create the workflow for the base DN and register the workflow with
+    // the server.
+    externalChangeLogWorkflow = new WorkflowImpl(
+        externalChangeLogWorkflowID,
+        DN.decode(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT),
+        eclwe.getWorkflowElementID(),
+        eclwe);
+    externalChangeLogWorkflow.register();
+
+    NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
+    defaultNetworkGroup.registerWorkflow(externalChangeLogWorkflow);
+
+    // FIXME:ECL should the ECL Workflow be registered in adminNetworkGroup?
+    NetworkGroup adminNetworkGroup = NetworkGroup.getAdminNetworkGroup();
+    adminNetworkGroup.registerWorkflow(externalChangeLogWorkflow);
+
+    // FIXME:ECL should the ECL Workflow be registered in internalNetworkGroup?
+    NetworkGroup internalNetworkGroup = NetworkGroup.getInternalNetworkGroup();
+    internalNetworkGroup.registerWorkflow(externalChangeLogWorkflow);
+
+}
+
+  private void finalizeECL()
+  {
+    WorkflowImpl eclwf =
+      (WorkflowImpl)WorkflowImpl.getWorkflow(externalChangeLogWorkflowID);
+
+    // do it only if not already done by another RS (unit test case)
+    // if (DirectoryServer.getWorkflowElement(externalChangeLogWorkflowID)
+    if (eclwf!=null)
+    {
+
+
+    // FIXME:ECL should the ECL Workflow be registered in internalNetworkGroup?
+    NetworkGroup internalNetworkGroup = NetworkGroup.getInternalNetworkGroup();
+    internalNetworkGroup.deregisterWorkflow(externalChangeLogWorkflowID);
+
+    // FIXME:ECL should the ECL Workflow be registered in adminNetworkGroup?
+    NetworkGroup adminNetworkGroup = NetworkGroup.getAdminNetworkGroup();
+    adminNetworkGroup.deregisterWorkflow(externalChangeLogWorkflowID);
+
+    NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
+    defaultNetworkGroup.deregisterWorkflow(externalChangeLogWorkflowID);
+
+    eclwf.deregister();
+    eclwf.finalizeWorkflow();
+    }
+
+    eclwe = (ECLWorkflowElement)
+    DirectoryServer.getWorkflowElement("EXTERNAL CHANGE LOG");
+    if (eclwe!=null)
+    {
+      DirectoryServer.deregisterWorkflowElement(eclwe);
+      eclwe.finalizeWorkflowElement();
+    }
+
+}
+
+  /**
    * Get the ReplicationServerDomain associated to the base DN given in
    * parameter.
    *
@@ -571,6 +711,8 @@
     {
       dbEnv.shutdown();
     }
+
+    finalizeECL();
 }
 
 
@@ -1220,6 +1362,38 @@
   }
 
   /**
+   * Returns the number of domains managed by this replication server.
+   * @return the number of domains managed.
+   */
+  public int getCacheSize()
+  {
+    return baseDNs.size();
+  }
+
+  /**
+   * Create a new session to get the ECL.
+   * @param msg The message that specifies the ECL request.
+   * @return Returns the created session.
+   * @throws DirectoryException When an error occurs.
+   */
+  public ExternalChangeLogSession createECLSession(StartECLSessionMsg msg)
+  throws DirectoryException
+  {
+    ExternalChangeLogSessionImpl session =
+      new ExternalChangeLogSessionImpl(this, msg);
+    return session;
+  }
+
+  /**
+   * Getter on the server URL.
+   * @return the server URL.
+   */
+  public String getServerURL()
+  {
+    return this.serverURL;
+  }
+
+  /**
    * This method allows to check if the Replication Server given
    * as the parameter is running in the local JVM.
    *
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 ea99e4b..76aa95b 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
@@ -26,56 +26,59 @@
  */
 package org.opends.server.replication.server;
 
-import org.opends.messages.Message;
-import org.opends.messages.MessageBuilder;
-
-import static org.opends.server.loggers.debug.DebugLogger.*;
-
-import org.opends.server.admin.std.server.MonitorProviderCfg;
-import org.opends.server.api.MonitorProvider;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.loggers.debug.DebugTracer;
-import static org.opends.server.loggers.ErrorLogger.logError;
 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.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.TimeUnit;
-import java.util.Iterator;
+import java.util.concurrent.locks.ReentrantLock;
 
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.messages.Severity;
+import org.opends.server.admin.std.server.MonitorProviderCfg;
+import org.opends.server.api.MonitorProvider;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.AssuredMode;
 import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.DSInfo;
+import org.opends.server.replication.common.RSInfo;
 import org.opends.server.replication.common.ServerState;
+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.ChangeStatusMsg;
 import org.opends.server.replication.protocol.ErrorMsg;
-import org.opends.server.replication.protocol.RoutableMsg;
-import org.opends.server.replication.protocol.UpdateMsg;
-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.ProtocolVersion;
 import org.opends.server.replication.protocol.ResetGenerationIdMsg;
+import org.opends.server.replication.protocol.RoutableMsg;
+import org.opends.server.replication.protocol.TopologyMsg;
+import org.opends.server.replication.protocol.UpdateMsg;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeBuilder;
 import org.opends.server.types.Attributes;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.ResultCode;
+import org.opends.server.util.TimeThread;
 import com.sleepycat.je.DatabaseException;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.locks.ReentrantLock;
-import org.opends.server.replication.common.AssuredMode;
-import org.opends.server.replication.common.DSInfo;
-import org.opends.server.replication.common.RSInfo;
-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.ChangeStatusMsg;
-import org.opends.server.replication.protocol.ProtocolVersion;
 
 /**
  * This class define an in-memory cache that will be used to store
@@ -111,8 +114,8 @@
    * to this replication server.
    *
    */
-  private final Map<Short, ServerHandler> directoryServers =
-    new ConcurrentHashMap<Short, ServerHandler>();
+  private final Map<Short, DataServerHandler> directoryServers =
+    new ConcurrentHashMap<Short, DataServerHandler>();
 
   /*
    * This map contains one ServerHandler for each replication servers
@@ -123,8 +126,11 @@
    * We add new TreeSet in the HashMap when a new replication server register
    * to this replication server.
    */
-  private final Map<Short, ServerHandler> replicationServers =
-    new ConcurrentHashMap<Short, ServerHandler>();
+  private final Map<Short, ReplicationServerHandler> replicationServers =
+    new ConcurrentHashMap<Short, ReplicationServerHandler>();
+
+  private final ConcurrentLinkedQueue<MessageHandler> otherHandlers =
+    new ConcurrentLinkedQueue<MessageHandler>();
 
   /*
    * This map contains the List of updates received from each
@@ -134,16 +140,14 @@
     new ConcurrentHashMap<Short, DbHandler>();
   private ReplicationServer replicationServer;
 
-  /* GenerationId management */
+  // GenerationId management
   private long generationId = -1;
   private boolean generationIdSavedStatus = false;
-  /**
-   * The tracer object for the debug logger.
-   */
+
+  // The tracer object for the debug logger.
   private static final DebugTracer TRACER = getTracer();
 
-  /* Monitor data management */
-
+  // Monitor data management
   /**
    * The monitor data consolidated over the topology.
    */
@@ -346,9 +350,9 @@
     /*
      * Push the message to the replication servers
      */
-    if (sourceHandler.isLDAPserver())
+    if (sourceHandler.isDataServer())
     {
-      for (ServerHandler handler : replicationServers.values())
+      for (ReplicationServerHandler handler : replicationServers.values())
       {
         /**
          * Ignore updates to RS with bad gen id
@@ -397,7 +401,7 @@
     /*
      * Push the message to the LDAP servers
      */
-    for (ServerHandler handler : directoryServers.values())
+    for (DataServerHandler handler : directoryServers.values())
     {
       // Don't forward the change to the server that just sent it
       if (handler == sourceHandler)
@@ -467,6 +471,14 @@
         handler.add(update, sourceHandler);
       }
     }
+
+    // Push the message to the other subscribing handlers
+    Iterator<MessageHandler> otherIter = otherHandlers.iterator();
+    while (otherIter.hasNext())
+    {
+      MessageHandler handler = otherIter.next();
+      handler.add(update, sourceHandler);
+    }
   }
 
   /**
@@ -522,10 +534,10 @@
     if (sourceGroupId == groupId)
       // Assured feature does not cross different group ids
     {
-      if (sourceHandler.isLDAPserver())
+      if (sourceHandler.isDataServer())
       {
         // Look for RS eligible for assured
-        for (ServerHandler handler : replicationServers.values())
+        for (ReplicationServerHandler handler : replicationServers.values())
         {
           if (handler.getGroupId() == groupId)
             // No ack expected from a RS with different group id
@@ -541,7 +553,7 @@
       }
 
       // Look for DS eligible for assured
-      for (ServerHandler handler : directoryServers.values())
+      for (DataServerHandler handler : directoryServers.values())
       {
         // Don't forward the change to the server that just sent it
         if (handler == sourceHandler)
@@ -636,7 +648,7 @@
         (generationId == sourceHandler.getGenerationId()))
         // Ignore assured updates from wrong generationId servers
       {
-        if (sourceHandler.isLDAPserver())
+        if (sourceHandler.isDataServer())
         {
           if (safeDataLevel == (byte) 1)
           {
@@ -689,10 +701,10 @@
     List<Short> expectedServers = new ArrayList<Short>();
     if (interestedInAcks)
     {
-      if (sourceHandler.isLDAPserver())
+      if (sourceHandler.isDataServer())
       {
         // Look for RS eligible for assured
-        for (ServerHandler handler : replicationServers.values())
+        for (ReplicationServerHandler handler : replicationServers.values())
         {
           if (handler.getGroupId() == groupId)
             // No ack expected from a RS with different group id
@@ -879,7 +891,7 @@
             origServer.incrementAssuredSrReceivedUpdatesTimeout();
           } else
           {
-            if (origServer.isLDAPserver())
+            if (origServer.isDataServer())
             {
               origServer.incrementAssuredSdReceivedUpdatesTimeout();
             }
@@ -957,7 +969,7 @@
    */
   public void stopReplicationServers(Collection<String> replServers)
   {
-    for (ServerHandler handler : replicationServers.values())
+    for (ReplicationServerHandler handler : replicationServers.values())
     {
       if (replServers.contains(handler.getServerAddressURL()))
         stopServer(handler);
@@ -970,13 +982,13 @@
   public void stopAllServers()
   {
     // Close session with other replication servers
-    for (ServerHandler serverHandler : replicationServers.values())
+    for (ReplicationServerHandler serverHandler : replicationServers.values())
     {
       stopServer(serverHandler);
     }
 
     // Close session with other LDAP servers
-    for (ServerHandler serverHandler : directoryServers.values())
+    for (DataServerHandler serverHandler : directoryServers.values())
     {
       stopServer(serverHandler);
     }
@@ -988,14 +1000,13 @@
    * @param handler the DS we want to check
    * @return true if this is not a duplicate server
    */
-  public boolean checkForDuplicateDS(ServerHandler handler)
+  public boolean checkForDuplicateDS(DataServerHandler handler)
   {
-    ServerHandler oldHandler = directoryServers.get(handler.getServerId());
+    DataServerHandler oldHandler = directoryServers.get(handler.getServerId());
 
     if (directoryServers.containsKey(handler.getServerId()))
     {
       // looks like two LDAP servers have the same serverId
-      // log an error message and drop this connection.
       Message message = ERR_DUPLICATE_SERVER_ID.get(
         replicationServer.getMonitorInstanceName(), oldHandler.toString(),
         handler.toString(), handler.getServerId());
@@ -1012,6 +1023,12 @@
    */
   public void stopServer(ServerHandler handler)
   {
+      if (debugEnabled())
+        TRACER.debugInfo(
+            "In RS " + this.replicationServer.getMonitorInstanceName() +
+            " domain=" + this +
+            " stopServer(SH)" + handler.getMonitorInstanceName() +
+          " " + stackTraceToSingleLineString(new Exception()));
     /*
      * We must prevent deadlock on replication server domain lock, when for
      * instance this code is called from dying ServerReader but also dying
@@ -1020,14 +1037,103 @@
      * or not (is already being processed or not).
      */
     if (!handler.engageShutdown())
-    // Only do this once (prevent other thread to enter here again)
+      // Only do this once (prevent other thread to enter here again)
     {
-      if (debugEnabled())
-        TRACER.debugInfo(
-          "In RS " + this.replicationServer.getMonitorInstanceName() +
-          " for " + baseDn + " " +
-          " stopServer " + handler.getMonitorInstanceName());
+      try
+      {
+        try
+        {
+          // Acquire lock on domain (see more details in comment of start()
+          // method of ServerHandler)
+          lock();
+        } catch (InterruptedException ex)
+        {
+          // Try doing job anyway...
+        }
 
+        if (handler.isReplicationServer())
+        {
+          if (replicationServers.containsValue(handler))
+          {
+            unregisterServerHandler(handler);
+            handler.shutdown();
+
+            // Check if generation id has to be resetted
+            mayResetGenerationId();
+            // Warn our DSs that a RS or DS has quit (does not use this
+            // handler as already removed from list)
+            buildAndSendTopoInfoToDSs(null);
+          }
+        } else
+        {
+          if (directoryServers.containsValue(handler))
+          {
+            // If this is the last DS for the domain,
+            // shutdown the status analyzer
+            if (directoryServers.size() == 1)
+            {
+              if (debugEnabled())
+                TRACER.debugInfo("In " +
+                    replicationServer.getMonitorInstanceName() +
+                    " remote server " + handler.getMonitorInstanceName() +
+                " is the last DS to be stopped: stopping status analyzer");
+              stopStatusAnalyzer();
+            }
+
+            unregisterServerHandler(handler);
+            handler.shutdown();
+
+            // Check if generation id has to be resetted
+            mayResetGenerationId();
+            // Update the remote replication servers with our list
+            // of connected LDAP servers
+            buildAndSendTopoInfoToRSs();
+            // Warn our DSs that a RS or DS has quit (does not use this
+            // handler as already removed from list)
+            buildAndSendTopoInfoToDSs(null);
+          }
+          else if (otherHandlers.contains(handler))
+          {
+            unRegisterHandler(handler);
+            handler.shutdown();
+          }
+        }
+
+      }
+      catch(Exception e)
+      {
+        logError(Message.raw(Category.SYNC, Severity.NOTICE,
+            stackTraceToSingleLineString(e)));
+      }
+      finally
+      {
+        release();
+      }
+    }
+  }
+
+  /**
+   * Stop the handler.
+   * @param handler The handler to stop.
+   */
+  public void stopServer(MessageHandler handler)
+  {
+    if (debugEnabled())
+      TRACER.debugInfo(
+          "In RS " + this.replicationServer.getMonitorInstanceName() +
+          " domain=" + this +
+          " stopServer(MH)" + handler.getMonitorInstanceName() +
+          " " + stackTraceToSingleLineString(new Exception()));
+    /*
+     * We must prevent deadlock on replication server domain lock, when for
+     * instance this code is called from dying ServerReader but also dying
+     * ServerWriter at the same time, or from a thread that wants to shut down
+     * the handler. So use a thread safe flag to know if the job must be done
+     * or not (is already being processed or not).
+     */
+    if (!handler.engageShutdown())
+      // Only do this once (prevent other thread to enter here again)
+    {
       try
       {
         // Acquire lock on domain (see more details in comment of start()
@@ -1037,57 +1143,40 @@
       {
         // Try doing job anyway...
       }
-
-      if (handler.isReplicationServer())
+      if (otherHandlers.contains(handler))
       {
-        if (replicationServers.containsValue(handler))
-        {
-          replicationServers.remove(handler.getServerId());
-          handler.shutdown();
-
-          // Check if generation id has to be resetted
-          mayResetGenerationId();
-          // Warn our DSs that a RS or DS has quit (does not use this
-          // handler as already removed from list)
-          sendTopoInfoToDSs(null);
-        }
-      } else
-      {
-        if (directoryServers.containsValue(handler))
-        {
-          // If this is the last DS for the domain, shutdown the status analyzer
-          if (directoryServers.size() == 1)
-          {
-            if (debugEnabled())
-              TRACER.debugInfo("In " +
-                replicationServer.getMonitorInstanceName() +
-                " remote server " + handler.getMonitorInstanceName() +
-                " is the last DS to be stopped: stopping status analyzer");
-            stopStatusAnalyzer();
-          }
-
-          directoryServers.remove(handler.getServerId());
-          handler.shutdown();
-
-          // Check if generation id has to be resetted
-          mayResetGenerationId();
-          // Update the remote replication servers with our list
-          // of connected LDAP servers
-          sendTopoInfoToRSs();
-          // Warn our DSs that a RS or DS has quit (does not use this
-          // handler as already removed from list)
-          sendTopoInfoToDSs(null);
-        }
+        unRegisterHandler(handler);
+        handler.shutdown();
       }
+    }
+    release();
+  }
 
-      release();
+  /**
+   * Unregister this handler from the list of handlers registered to this
+   * domain.
+   * @param handler the provided handler to unregister.
+   */
+  protected void unregisterServerHandler(ServerHandler handler)
+  {
+    if (handler.isReplicationServer())
+    {
+      replicationServers.remove(handler.getServerId());
+    }
+    else
+    {
+      directoryServers.remove(handler.getServerId());
     }
   }
 
   /**
-   * Resets the generationId for this domain if there is no LDAP
-   * server currently connected and if the generationId has never
-   * been saved.
+   * This method resets the generationId for this domain if there is no LDAP
+   * server currently connected in the whole topology on this domain and
+   * if the generationId has never been saved.
+   *
+   * - test emtpyness of directoryServers list
+   * - traverse replicationServers list and test for each if DS are connected
+   * So it strongly relies on the directoryServers list
    */
   protected void mayResetGenerationId()
   {
@@ -1104,7 +1193,7 @@
     boolean lDAPServersConnectedInTheTopology = false;
     if (directoryServers.isEmpty())
     {
-      for (ServerHandler rsh : replicationServers.values())
+      for (ReplicationServerHandler rsh : replicationServers.values())
       {
         if (generationId != rsh.getGenerationId())
         {
@@ -1149,14 +1238,16 @@
   }
 
   /**
-   * Checks that a RS is not already connected.
-   *
-   * @param handler the RS we want to check
-   * @return true if this is not a duplicate server
+   * Checks that a remote RS is not already connected to this hosting RS.
+   * @param handler The handler for the remote RS.
+   * @return flag specifying whether the remote RS is already connected.
+   * @throws DirectoryException when a problem occurs.
    */
-  public boolean checkForDuplicateRS(ServerHandler handler)
+  public boolean checkForDuplicateRS(ReplicationServerHandler handler)
+  throws DirectoryException
   {
-    ServerHandler oldHandler = replicationServers.get(handler.getServerId());
+    ReplicationServerHandler oldHandler =
+      replicationServers.get(handler.getServerId());
     if ((oldHandler != null))
     {
       if (oldHandler.getServerAddressURL().equals(
@@ -1166,7 +1257,9 @@
         // have been sent at about the same time and 2 connections
         // have been established.
         // Silently drop this connection.
-        } else
+        return false;
+      }
+      else
       {
         // looks like two replication servers have the same serverId
         // log an error message and drop this connection.
@@ -1174,9 +1267,8 @@
           replicationServer.getMonitorInstanceName(), oldHandler.
           getServerAddressURL(), handler.getServerAddressURL(),
           handler.getServerId());
-        logError(message);
+        throw new DirectoryException(ResultCode.OTHER, message);
       }
-      return false;
     }
     return true;
   }
@@ -1223,7 +1315,7 @@
   {
     LinkedHashSet<String> mySet = new LinkedHashSet<String>();
 
-    for (ServerHandler handler : replicationServers.values())
+    for (ReplicationServerHandler handler : replicationServers.values())
     {
       mySet.add(handler.getServerAddressURL());
     }
@@ -1232,7 +1324,9 @@
   }
 
   /**
-   * Return a Set containing the servers known by this replicationServer.
+   * Return a set containing the server that produced update and known by
+   * this replicationServer from all over the topology,
+   * whatever directly connected of connected to another RS.
    * @return a set containing the servers known by this replicationServer.
    */
   public Set<Short> getServers()
@@ -1250,7 +1344,7 @@
   {
     List<String> mySet = new ArrayList<String>(0);
 
-    for (ServerHandler handler : directoryServers.values())
+    for (DataServerHandler handler : directoryServers.values())
     {
       mySet.add(String.valueOf(handler.getServerId()));
     }
@@ -1348,7 +1442,7 @@
       {
         // Send to all replication servers with a least one remote
         // server connected
-        for (ServerHandler rsh : replicationServers.values())
+        for (ReplicationServerHandler rsh : replicationServers.values())
         {
           if (rsh.hasRemoteLDAPServers())
           {
@@ -1358,7 +1452,7 @@
       }
 
       // Sends to all connected LDAP servers
-      for (ServerHandler destinationHandler : directoryServers.values())
+      for (DataServerHandler destinationHandler : directoryServers.values())
       {
         // Don't loop on the sender
         if (destinationHandler == senderHandler)
@@ -1368,7 +1462,7 @@
     } else
     {
       // Destination is one server
-      ServerHandler destinationHandler =
+      DataServerHandler destinationHandler =
         directoryServers.get(msg.getDestination());
       if (destinationHandler != null)
       {
@@ -1378,9 +1472,9 @@
         // the targeted server is NOT connected
         // Let's search for THE changelog server that MAY
         // have the targeted server connected.
-        if (senderHandler.isLDAPserver())
+        if (senderHandler.isDataServer())
         {
-          for (ServerHandler h : replicationServers.values())
+          for (ReplicationServerHandler h : replicationServers.values())
           {
             // Send to all replication servers with a least one remote
             // server connected
@@ -1421,7 +1515,7 @@
         // build the full list of all servers in the topology
         // and send back a MonitorMsg with the full list of all the servers
         // in the topology.
-        if (senderHandler.isLDAPserver())
+        if (senderHandler.isDataServer())
         {
           MonitorMsg returnMsg =
             new MonitorMsg(msg.getDestination(), msg.getsenderID());
@@ -1481,7 +1575,7 @@
         // from the states stored in the serverHandler.
         // - the server state
         // - the older missing change
-        for (ServerHandler lsh : this.directoryServers.values())
+        for (DataServerHandler lsh : this.directoryServers.values())
         {
           monitorMsg.setServerState(
             lsh.getServerId(),
@@ -1491,7 +1585,7 @@
         }
 
         // Same for the connected RS
-        for (ServerHandler rsh : this.replicationServers.values())
+        for (ReplicationServerHandler rsh : this.replicationServers.values())
         {
           monitorMsg.setServerState(
             rsh.getServerId(),
@@ -1657,12 +1751,12 @@
    */
   public void checkAllSaturation() throws IOException
   {
-    for (ServerHandler handler : replicationServers.values())
+    for (ReplicationServerHandler handler : replicationServers.values())
     {
       handler.checkWindow();
     }
 
-    for (ServerHandler handler : directoryServers.values())
+    for (DataServerHandler handler : directoryServers.values())
     {
       handler.checkWindow();
     }
@@ -1675,15 +1769,15 @@
    * @return true if the server can restart sending changes.
    *         false if the server can't restart sending changes.
    */
-  public boolean restartAfterSaturation(ServerHandler sourceHandler)
+  public boolean restartAfterSaturation(MessageHandler sourceHandler)
   {
-    for (ServerHandler handler : replicationServers.values())
+    for (MessageHandler handler : replicationServers.values())
     {
       if (!handler.restartAfterSaturation(sourceHandler))
         return false;
     }
 
-    for (ServerHandler handler : directoryServers.values())
+    for (MessageHandler handler : directoryServers.values())
     {
       if (!handler.restartAfterSaturation(sourceHandler))
         return false;
@@ -1698,9 +1792,9 @@
    * @param notThisOne If not null, the topology message will not be sent to
    * this passed server.
    */
-  public void sendTopoInfoToDSs(ServerHandler notThisOne)
+  public void buildAndSendTopoInfoToDSs(ServerHandler notThisOne)
   {
-    for (ServerHandler handler : directoryServers.values())
+    for (DataServerHandler handler : directoryServers.values())
     {
       if ((notThisOne == null) || // All DSs requested
         ((notThisOne != null) && (handler != notThisOne)))
@@ -1725,10 +1819,10 @@
    * Send a TopologyMsg to all the connected replication servers
    * in order to let them know our connected LDAP servers.
    */
-  public void sendTopoInfoToRSs()
+  public void buildAndSendTopoInfoToRSs()
   {
     TopologyMsg topoMsg = createTopologyMsgForRS();
-    for (ServerHandler handler : replicationServers.values())
+    for (ReplicationServerHandler handler : replicationServers.values())
     {
       try
       {
@@ -1755,7 +1849,7 @@
     List<DSInfo> dsInfos = new ArrayList<DSInfo>();
 
     // Go through every DSs
-    for (ServerHandler serverHandler : directoryServers.values())
+    for (DataServerHandler serverHandler : directoryServers.values())
     {
       dsInfos.add(serverHandler.toDSInfo());
     }
@@ -1785,7 +1879,7 @@
     List<RSInfo> rsInfos = new ArrayList<RSInfo>();
 
     // Go through every DSs (except recipient of msg)
-    for (ServerHandler serverHandler : directoryServers.values())
+    for (DataServerHandler serverHandler : directoryServers.values())
     {
       if (serverHandler.getServerId() == destDsId)
         continue;
@@ -1799,7 +1893,7 @@
 
     // Go through every peer RSs (and get their connected DSs), also add info
     // for RSs
-    for (ServerHandler serverHandler : replicationServers.values())
+    for (ReplicationServerHandler serverHandler : replicationServers.values())
     {
       // Put RS info
       rsInfos.add(serverHandler.toRSInfo());
@@ -1840,12 +1934,6 @@
   synchronized public long setGenerationId(long generationId,
     boolean savedStatus)
   {
-    if (debugEnabled())
-      TRACER.debugInfo(
-        "In " + this.replicationServer.getMonitorInstanceName() +
-        " baseDN=" + baseDn +
-        " RCache.set GenerationId=" + generationId);
-
     long oldGenerationId = this.generationId;
 
     if (this.generationId != generationId)
@@ -1916,7 +2004,7 @@
         // After we'll have sent the message , the remote RS will adopt
         // the new genId
         rsHandler.setGenerationId(newGenId);
-        if (senderHandler.isLDAPserver())
+        if (senderHandler.isDataServer())
         {
           rsHandler.forwardGenerationIdToRS(genIdMsg);
         }
@@ -1929,7 +2017,7 @@
 
     // Change status of the connected DSs according to the requested new
     // reference generation id
-    for (ServerHandler dsHandler : directoryServers.values())
+    for (DataServerHandler dsHandler : directoryServers.values())
     {
       try
       {
@@ -1948,8 +2036,8 @@
     // (consecutive to reset gen id message), we prefer advertising once for
     // all after changes (less packet sent), here at the end of the reset msg
     // treatment.
-    sendTopoInfoToDSs(null);
-    sendTopoInfoToRSs();
+    buildAndSendTopoInfoToDSs(null);
+    buildAndSendTopoInfoToRSs();
 
     Message message = NOTE_RESET_GENERATION_ID.get(baseDn.toString(),
       Long.toString(newGenId));
@@ -1964,7 +2052,7 @@
    *        that changed his status.
    * @param csMsg The message containing the new status
    */
-  public void processNewStatus(ServerHandler senderHandler,
+  public void processNewStatus(DataServerHandler senderHandler,
     ChangeStatusMsg csMsg)
   {
     if (debugEnabled())
@@ -1995,8 +2083,8 @@
     }
 
     // Update every peers (RS/DS) with topology changes
-    sendTopoInfoToDSs(senderHandler);
-    sendTopoInfoToRSs();
+    buildAndSendTopoInfoToDSs(senderHandler);
+    buildAndSendTopoInfoToRSs();
 
     Message message = NOTE_DIRECTORY_SERVER_CHANGED_STATUS.get(
       Short.toString(senderHandler.getServerId()),
@@ -2014,7 +2102,7 @@
    * @param event The event to be used for new status computation
    * @return True if we have been interrupted (must stop), false otherwise
    */
-  public boolean changeStatusFromStatusAnalyzer(ServerHandler serverHandler,
+  public boolean changeStatusFromStatusAnalyzer(DataServerHandler serverHandler,
     StatusMachineEvent event)
   {
     try
@@ -2066,8 +2154,8 @@
     }
 
     // Update every peers (RS/DS) with topology changes
-    sendTopoInfoToDSs(serverHandler);
-    sendTopoInfoToRSs();
+    buildAndSendTopoInfoToDSs(serverHandler);
+    buildAndSendTopoInfoToRSs();
 
     release();
     return false;
@@ -2169,10 +2257,12 @@
    * @param allowResetGenId True for allowing to reset the generation id (
    * when called after initial handshake)
    * @throws IOException If an error occurred.
+   * @throws DirectoryException If an error occurred.
    */
-  public void receiveTopoInfoFromRS(TopologyMsg topoMsg, ServerHandler handler,
+  public void receiveTopoInfoFromRS(TopologyMsg topoMsg,
+      ReplicationServerHandler handler,
     boolean allowResetGenId)
-    throws IOException
+    throws IOException, DirectoryException
   {
     if (debugEnabled())
     {
@@ -2186,7 +2276,8 @@
     {
       // Acquire lock on domain (see more details in comment of start() method
       // of ServerHandler)
-      lock();
+      if (!hasLock())
+        lock();
     } catch (InterruptedException ex)
     {
       // Try doing job anyway...
@@ -2228,7 +2319,7 @@
      * Sends the currently known topology information to every connected
      * DS we have.
      */
-    sendTopoInfoToDSs(null);
+    buildAndSendTopoInfoToDSs(null);
 
     release();
   }
@@ -2441,7 +2532,7 @@
           {
             // this is the latency of the remote RSi regarding another RSj
             // let's update the latency of the LSes connected to RSj
-            ServerHandler rsjHdr = replicationServers.get(rsid);
+            ReplicationServerHandler rsjHdr = replicationServers.get(rsid);
             if (rsjHdr != null)
             {
               for (short remotelsid : rsjHdr.getConnectedDirectoryServerIds())
@@ -2496,7 +2587,7 @@
    * Get the map of connected DSs.
    * @return The map of connected DSs
    */
-  public Map<Short, ServerHandler> getConnectedDSs()
+  public Map<Short, DataServerHandler> getConnectedDSs()
   {
     return directoryServers;
   }
@@ -2505,7 +2596,7 @@
    * Get the map of connected RSs.
    * @return The map of connected RSs
    */
-  public Map<Short, ServerHandler> getConnectedRSs()
+  public Map<Short, ReplicationServerHandler> getConnectedRSs()
   {
     return replicationServers;
   }
@@ -2708,5 +2799,140 @@
 
     return attributes;
   }
-}
 
+  /**
+   * Register in the domain an handler that subscribes to changes.
+   * @param handler the provided subscribing handler.
+   */
+  public void registerHandler(MessageHandler handler)
+  {
+    this.otherHandlers.add(handler);
+  }
+
+  /**
+   * Unregister from the domain an handler.
+   * @param handler the provided unsubscribing handler.
+   * @return Whether this handler has been unregistered with success.
+   */
+  public boolean unRegisterHandler(MessageHandler handler)
+  {
+    return this.otherHandlers.remove(handler);
+  }
+
+  /**
+   * Return the state that contain for each server the time of eligibility.
+   * @return the state.
+   */
+  public ServerState getHeartbeatState()
+  {
+    // TODO:ECL Eligility must be supported
+    return this.getDbServerState();
+  }
+  /**
+   * Computes the change number eligible to the ECL.
+   * @return null if the domain does not play in eligibility.
+   */
+  public ChangeNumber computeEligibleCN()
+  {
+    ChangeNumber elligibleCN = null;
+    ServerState heartbeatState = 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." + this.baseDn + " Server " + sid
+            + " is not considered for eligibility ... potentially down");
+        continue;
+      }
+
+      if ((elligibleCN == null) || (storedCN.older(elligibleCN)))
+      {
+        elligibleCN = storedCN;
+      }
+    }
+
+    if (debugEnabled())
+      TRACER.debugInfo(
+        "For RSD." + this.baseDn + " ElligibleCN()=" + elligibleCN);
+    return elligibleCN;
+  }
+
+  /**
+   * Computes the eligible server state by minimizing the dbServerState and the
+   * elligibleCN.
+   * @return The computed eligible server state.
+   */
+  public ServerState getCLElligibleState()
+  {
+    // ChangeNumber elligibleCN = computeEligibleCN();
+    ServerState res = new ServerState();
+    ServerState dbState = this.getDbServerState();
+    res = dbState;
+
+    /* TODO:ECL Eligibility is not yet implemented
+    Iterator<Short> it = dbState.iterator();
+    while (it.hasNext())
+    {
+      Short sid = it.next();
+      DbHandler h = sourceDbHandlers.get(sid);
+      ChangeNumber dbCN = dbState.getMaxChangeNumber(sid);
+      try
+      {
+        if ((elligibleCN!=null)&&(elligibleCN.older(dbCN)))
+        {
+          // some CN exist in the db newer than elligible CN
+          ReplicationIterator ri = h.generateIterator(elligibleCN);
+          ChangeNumber newCN = ri.getCurrentCN();
+          res.update(newCN);
+          ri.releaseCursor();
+        }
+        else
+        {
+          // no CN exist in the db newer than elligible CN
+          res.update(dbCN);
+        }
+      }
+      catch(Exception e)
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+    }
+    */
+
+    if (debugEnabled())
+      TRACER.debugInfo("In " + this.getName()
+        + " getCLElligibleState returns:" + res);
+    return res;
+  }
+
+  /**
+   * Returns the start state of the domain, made of the first (oldest)
+   * change stored for each serverId.
+   * @return t start state of the domain.
+   */
+  public ServerState getStartState()
+  {
+    ServerState domainStartState = new ServerState();
+    Iterator<Short> it = this.getDbServerState().iterator();
+    for (DbHandler dbHandler : sourceDbHandlers.values())
+    {
+      domainStartState.update(dbHandler.getFirstChange());
+    }
+    return domainStartState;
+  }
+}
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
new file mode 100644
index 0000000..7b85671
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java
@@ -0,0 +1,791 @@
+/*
+ * 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.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.util.StaticUtils.stackTraceToSingleLineString;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.opends.messages.Message;
+import org.opends.server.replication.common.DSInfo;
+import org.opends.server.replication.common.RSInfo;
+import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.common.ServerStatus;
+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.ReplicationMsg;
+import org.opends.server.replication.protocol.TopologyMsg;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeBuilder;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+
+/**
+ * This class defines a server handler, which handles all interaction with a
+ * peer replication server.
+ */
+public class ReplicationServerHandler extends ServerHandler
+{
+
+  /*
+   * Properties filled only if remote server is a RS
+   */
+  private String serverAddressURL;
+  /**
+   * this collection will contain as many elements as there are
+   * LDAP servers connected to the remote replication server.
+   */
+  private final Map<Short, LightweightServerHandler> remoteDirectoryServers =
+    new ConcurrentHashMap<Short, LightweightServerHandler>();
+
+  /**
+   * Starts this handler based on a start message received from remote server.
+   * @param inReplServerStartMsg The start msg provided by the remote server.
+   * @return Whether the remote server requires encryption or not.
+   * @throws DirectoryException When a problem occurs.
+   */
+  public boolean processStartFromRemote(ReplServerStartMsg inReplServerStartMsg)
+  throws DirectoryException
+  {
+    try
+    {
+      protocolVersion = ProtocolVersion.minWithCurrent(
+          inReplServerStartMsg.getVersion());
+      generationId = inReplServerStartMsg.getGenerationId();
+      serverId = inReplServerStartMsg.getServerId();
+      serverURL = inReplServerStartMsg.getServerURL();
+      int separator = serverURL.lastIndexOf(':');
+      serverAddressURL =
+        session.getRemoteAddress() + ":" + serverURL.substring(separator +
+            1);
+      setServiceIdAndDomain(inReplServerStartMsg.getBaseDn());
+      setInitialServerState(inReplServerStartMsg.getServerState());
+      setSendWindowSize(inReplServerStartMsg.getWindowSize());
+      if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
+      {
+        // We support connection from a V1 RS
+        // Only V2 protocol has the group id in repl server start message
+        this.groupId = inReplServerStartMsg.getGroupId();
+      }
+
+      oldGenerationId = -100;
+
+      // Duplicate server ?
+      if (!replicationServerDomain.checkForDuplicateRS(this))
+      {
+        abortStart(null);
+        return false;
+      }
+    }
+    catch(Exception e)
+    {
+      Message message = Message.raw(e.getLocalizedMessage());
+      throw new DirectoryException(ResultCode.OTHER, message);
+    }
+    return inReplServerStartMsg.getSSLEncryption();
+  }
+
+  /**
+   * Creates a new handler object to a remote replication server.
+   * @param session The session with the remote RS.
+   * @param queueSize The queue size to manage updates to that RS.
+   * @param replicationServerURL The hosting local RS URL.
+   * @param replicationServerId The hosting local RS serverId.
+   * @param replicationServer The hosting local RS object.
+   * @param rcvWindowSize The receiving window size.
+   */
+  public ReplicationServerHandler(
+      ProtocolSession session,
+      int queueSize,
+      String replicationServerURL,
+      short replicationServerId,
+      ReplicationServer replicationServer,
+      int rcvWindowSize)
+  {
+    super(session, queueSize, replicationServerURL, replicationServerId,
+        replicationServer, rcvWindowSize);
+  }
+
+  /**
+   * Connect the hosting RS to the RS represented by THIS handler
+   * on an outgoing connection.
+   * @param serviceId The serviceId (usually baseDn).
+   * @param sslEncryption The sslEncryption requested to the remote RS.
+   * @throws DirectoryException when an error occurs.
+   */
+  public void connect(String serviceId, boolean sslEncryption)
+  throws DirectoryException
+  {
+
+    //
+    // the encryption we will request to the peer as we are the session creator
+    this.initSslEncryption = sslEncryption;
+
+    setServiceIdAndDomain(serviceId);
+
+    localGenerationId = replicationServerDomain.getGenerationId();
+    oldGenerationId = localGenerationId;
+
+    try
+    {
+      //
+      lockDomain(false); // notimeout
+
+      // we are the initiator and decides of the encryption
+      boolean sessionInitiatorSSLEncryption = this.initSslEncryption;
+
+      // Send start
+      ReplServerStartMsg outReplServerStartMsg = sendStartToRemote((short)-1);
+
+      // Wait answer
+      ReplicationMsg msg = session.receive();
+
+      // Reject bad responses
+      if (!(msg instanceof ReplServerStartMsg))
+      {
+        Message message = ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE.get(
+            msg.getClass().getCanonicalName(),
+            "ReplServerStartMsg");
+        abortStart(message);
+        return;
+      }
+
+      // Process hello from remote
+      processStartFromRemote((ReplServerStartMsg)msg);
+
+      // Log
+      logStartHandshakeSNDandRCV(outReplServerStartMsg,(ReplServerStartMsg)msg);
+
+      // Until here session is encrypted then it depends on the negociation
+      // The session initiator decides whether to use SSL.
+      if (!sessionInitiatorSSLEncryption)
+        session.stopEncryption();
+
+      if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
+      {
+        // Only protocol version above V1 has a phase 2 handshake
+
+        // NOW PROCEDE WITH SECOND PHASE OF HANDSHAKE:
+        // TopologyMsg then TopologyMsg (with a RS)
+
+        // Send our own TopologyMsg to remote RS
+        TopologyMsg outTopoMsg = sendTopoToRemoteRS();
+
+        // wait and process Topo from remote RS
+        TopologyMsg inTopoMsg = waitAndProcessTopoFromRemoteRS();
+
+        logTopoHandshakeSNDandRCV(outTopoMsg, inTopoMsg);
+
+        // FIXME: i think this should be done for all protocol version !!
+        // not only those > V1
+        registerIntoDomain();
+
+        // Process TopologyMsg sent by remote RS: store matching new info
+        // (this will also warn our connected DSs of the new received info)
+        replicationServerDomain.receiveTopoInfoFromRS(inTopoMsg, this, false);
+      }
+      super.finalizeStart();
+
+    }
+    catch(IOException ioe)
+    {
+      // FIXME receive
+    }
+    // catch(DirectoryException de)
+    //{ already logged
+    //
+    catch(Exception e)
+    {
+      // FIXME more detailed exceptions
+    }
+    finally
+    {
+      // Release domain
+      if ((replicationServerDomain != null) &&
+          replicationServerDomain.hasLock())
+        replicationServerDomain.release();
+    }
+  }
+
+  /**
+   * Starts the handler from a remote ReplServerStart message received from
+   * the remote replication server.
+   * @param inReplServerStartMsg The provided ReplServerStart message received.
+   */
+  public void startFromRemoteRS(ReplServerStartMsg inReplServerStartMsg)
+  {
+    //
+    localGenerationId = -1;
+    oldGenerationId = -100;
+    try
+    {
+      // Process start from remote
+      boolean sessionInitiatorSSLEncryption =
+        processStartFromRemote(inReplServerStartMsg);
+
+      // lock with timeout
+      lockDomain(true);
+
+      short reqVersion = -1;
+      if (protocolVersion == ProtocolVersion.REPLICATION_PROTOCOL_V1)
+      {
+        // We support connection from a V1 RS, send PDU with V1 form
+        reqVersion = ProtocolVersion.REPLICATION_PROTOCOL_V1;
+      }
+
+      // send start to remote
+      ReplServerStartMsg outReplServerStartMsg = sendStartToRemote(reqVersion);
+
+      // log
+      logStartHandshakeRCVandSND(inReplServerStartMsg, outReplServerStartMsg);
+
+      // until here session is encrypted then it depends on the negociation
+      // The session initiator decides whether to use SSL.
+      if (!sessionInitiatorSSLEncryption)
+        session.stopEncryption();
+
+      TopologyMsg inTopoMsg = null;
+      if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
+      {
+        // Only protocol version above V1 has a phase 2 handshake
+        // NOW PROCEDE WITH SECOND PHASE OF HANDSHAKE:
+        // TopologyMsg then TopologyMsg (with a RS)
+
+        // wait and process Topo from remote RS
+        inTopoMsg = waitAndProcessTopoFromRemoteRS();
+
+        // send our own TopologyMsg to remote RS
+        TopologyMsg outTopoMsg = sendTopoToRemoteRS();
+
+        // log
+        logTopoHandshakeRCVandSND(inTopoMsg, outTopoMsg);
+      }
+      else
+      {
+        // Terminate connection from a V1 RS
+
+        // if the remote RS and the local RS have the same genID
+        // then it's ok and nothing else to do
+        if (generationId == localGenerationId)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugInfo("In " +
+              replicationServerDomain.getReplicationServer().
+              getMonitorInstanceName() +
+              this + " RS V1 with serverID=" + serverId +
+              " is connected with the right generation ID");
+          }
+        } else
+        {
+          if (localGenerationId > 0)
+          {
+            // if the local RS is initialized
+            if (generationId > 0)
+            {
+              // if the remote RS is initialized
+              if (generationId != localGenerationId)
+              {
+                // if the 2 RS have different generationID
+                if (replicationServerDomain.getGenerationIdSavedStatus())
+                {
+                  // if the present RS has received changes regarding its
+                  //     gen ID and so won't change without a reset
+                  // then  we are just degrading the peer.
+                  Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
+                    getServiceId(),
+                    Short.toString(serverId),
+                    Long.toString(generationId),
+                    Long.toString(localGenerationId));
+                  logError(message);
+                } else
+                {
+                  // The present RS has never received changes regarding its
+                  // gen ID.
+                  //
+                  // Example case:
+                  // - we are in RS1
+                  // - RS2 has genId2 from LS2 (genId2 <=> no data in LS2)
+                  // - RS1 has genId1 from LS1 /genId1 comes from data in
+                  //   suffix
+                  // - we are in RS1 and we receive a START msg from RS2
+                  // - Each RS keeps its genID / is degraded and when LS2
+                  //   will be populated from LS1 everything will become ok.
+                  //
+                  // Issue:
+                  // FIXME : Would it be a good idea in some cases to just
+                  //         set the gen ID received from the peer RS
+                  //         specially if the peer has a non null state and
+                  //         we have a nul state ?
+                  // replicationServerDomain.
+                  // setGenerationId(generationId, false);
+                  Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
+                    getServiceId(),
+                    Short.toString(serverId),
+                    Long.toString(generationId),
+                    Long.toString(localGenerationId));
+                  logError(message);
+                }
+              }
+            } else
+            {
+              // The remote RS has no genId. We don't change anything for the
+              // current RS.
+            }
+          } else
+          {
+            // The local RS is not initialized - take the one received
+            oldGenerationId =
+              replicationServerDomain.setGenerationId(generationId, false);
+          }
+        }
+
+
+        // Note: the supported scenario for V1->V2 upgrade is to upgrade 1 by 1
+        // all the servers of the topology. We prefer not not send a TopologyMsg
+        // for giving partial/false information to the V2 servers as for
+        // instance we don't have the connected DS of the V1 RS...When the V1
+        // RS will be upgraded in his turn, topo info will be sent and accurate.
+        // That way, there is  no risk to have false/incomplete information in
+        // other servers.
+      }
+
+      registerIntoDomain();
+
+      // Process TopologyMsg sent by remote RS: store matching new info
+      // (this will also warn our connected DSs of the new received info)
+      if (inTopoMsg!=null)
+        replicationServerDomain.receiveTopoInfoFromRS(inTopoMsg, this, false);
+
+      super.finalizeStart();
+
+    }
+    catch(DirectoryException de)
+    {
+      abortStart(de.getMessageObject());
+    }
+    catch(Exception e)
+    {
+      abortStart(Message.raw(e.getLocalizedMessage()));
+    }
+    finally
+    {
+      if ((replicationServerDomain != null) &&
+          replicationServerDomain.hasLock())
+        replicationServerDomain.release();
+    }
+  }
+
+  /**
+   * Registers this handler into its related domain and notifies the domain.
+   */
+  private void registerIntoDomain()
+  {
+    // Alright, connected with new RS (either outgoing or incoming
+    // connection): store handler.
+    Map<Short, ReplicationServerHandler> connectedRSs =
+      replicationServerDomain.getConnectedRSs();
+    connectedRSs.put(serverId, this);
+  }
+
+  /**
+   * Create and send the topologyMsg to the remote replication server.
+   * @return the topologyMsg sent.
+   */
+  private TopologyMsg sendTopoToRemoteRS()
+  throws IOException
+  {
+    TopologyMsg outTopoMsg = replicationServerDomain.createTopologyMsgForRS();
+    session.publish(outTopoMsg);
+    return outTopoMsg;
+  }
+
+  /**
+   * Wait receiving the TopologyMsg from the remote RS and process it.
+   * @return the topologyMsg received.
+   * @throws DirectoryException
+   * @throws IOException
+   */
+  private TopologyMsg waitAndProcessTopoFromRemoteRS()
+  throws DirectoryException, IOException
+  {
+    ReplicationMsg msg = null;
+    try
+    {
+      msg = session.receive();
+    }
+    catch(Exception e)
+    {
+      Message message = Message.raw(e.getLocalizedMessage());
+      throw new DirectoryException(ResultCode.OTHER, message);
+    }
+
+    if (!(msg instanceof TopologyMsg))
+    {
+      Message message = ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE.get(
+          msg.getClass().getCanonicalName(),
+          "TopologyMsg");
+      abortStart(message);
+    }
+
+    // Remote RS sent his topo msg
+    TopologyMsg inTopoMsg = (TopologyMsg) msg;
+
+    // CONNECTION WITH A RS
+
+    // if the remote RS and the local RS have the same genID
+    // then it's ok and nothing else to do
+    if (generationId == localGenerationId)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo("In " +
+            replicationServerDomain.getReplicationServer().
+            getMonitorInstanceName() + " RS with serverID=" + serverId +
+            " is connected with the right generation ID, same as local ="
+            + generationId);
+      }
+    }
+    else
+    {
+      if (localGenerationId > 0)
+      {
+        // if the local RS is initialized
+        if (generationId > 0)
+        {
+          // if the remote RS is initialized
+          if (generationId != localGenerationId)
+          {
+            // if the 2 RS have different generationID
+            if (replicationServerDomain.getGenerationIdSavedStatus())
+            {
+              // if the present RS has received changes regarding its
+              //     gen ID and so won't change without a reset
+              // then  we are just degrading the peer.
+              Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
+                  getServiceId(),
+                  Short.toString(serverId),
+                  Long.toString(generationId),
+                  Long.toString(localGenerationId));
+              logError(message);
+            }
+            else
+            {
+              // The present RS has never received changes regarding its
+              // gen ID.
+              //
+              // Example case:
+              // - we are in RS1
+              // - RS2 has genId2 from LS2 (genId2 <=> no data in LS2)
+              // - RS1 has genId1 from LS1 /genId1 comes from data in
+              //   suffix
+              // - we are in RS1 and we receive a START msg from RS2
+              // - Each RS keeps its genID / is degraded and when LS2
+              //   will be populated from LS1 everything will become ok.
+              //
+              // Issue:
+              // FIXME : Would it be a good idea in some cases to just
+              //         set the gen ID received from the peer RS
+              //         specially if the peer has a non null state and
+              //         we have a nul state ?
+              // replicationServerDomain.
+              // setGenerationId(generationId, false);
+              Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
+                  getServiceId(),
+                  Short.toString(serverId),
+                  Long.toString(generationId),
+                  Long.toString(localGenerationId));
+              logError(message);
+            }
+          }
+        }
+        else
+        {
+          // The remote RS has no genId. We don't change anything for the
+          // current RS.
+        }
+      }
+      else
+      {
+        // The local RS is not initialized - take the one received
+        // WARNING: Must be done before computing topo message to send
+        // to peer server as topo message must embed valid generation id
+        // for our server
+        oldGenerationId =
+          replicationServerDomain.setGenerationId(generationId, false);
+      }
+    }
+
+    return inTopoMsg;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isDataServer()
+  {
+    return false;
+  }
+
+  /**
+   * Add the DSinfos of the connected Directory Servers
+   * to the List of DSInfo provided as a parameter.
+   *
+   * @param dsInfos The List of DSInfo that should be updated
+   *                with the DSInfo for the remoteDirectoryServers
+   *                connected to this ServerHandler.
+   */
+  public void addDSInfos(List<DSInfo> dsInfos)
+  {
+    synchronized (remoteDirectoryServers)
+    {
+      for (LightweightServerHandler ls : remoteDirectoryServers.values())
+      {
+        dsInfos.add(ls.toDSInfo());
+      }
+    }
+  }
+
+  /**
+   * Shutdown This ServerHandler.
+   */
+  public void shutdown()
+  {
+    super.shutdown();
+    /*
+     * Stop the remote LSHandler
+     */
+    synchronized (remoteDirectoryServers)
+    {
+      for (LightweightServerHandler lsh : remoteDirectoryServers.values())
+      {
+        lsh.stopHandler();
+      }
+      remoteDirectoryServers.clear();
+    }
+  }
+  /**
+   * Stores topology information received from a peer RS and that must be kept
+   * in RS handler.
+   *
+   * @param topoMsg The received topology message
+   */
+  public void receiveTopoInfoFromRS(TopologyMsg topoMsg)
+  {
+    // Store info for remote RS
+    List<RSInfo> rsInfos = topoMsg.getRsList();
+    // List should only contain RS info for sender
+    RSInfo rsInfo = rsInfos.get(0);
+    generationId = rsInfo.getGenerationId();
+    groupId = rsInfo.getGroupId();
+
+    /**
+     * Store info for DSs connected to the peer RS
+     */
+    List<DSInfo> dsInfos = topoMsg.getDsList();
+
+    synchronized (remoteDirectoryServers)
+    {
+      // Removes the existing structures
+      for (LightweightServerHandler lsh : remoteDirectoryServers.values())
+      {
+        lsh.stopHandler();
+      }
+      remoteDirectoryServers.clear();
+
+      // Creates the new structure according to the message received.
+      for (DSInfo dsInfo : dsInfos)
+      {
+        LightweightServerHandler lsh = new LightweightServerHandler(this,
+            serverId, dsInfo.getDsId(), dsInfo.getGenerationId(),
+            dsInfo.getGroupId(), dsInfo.getStatus(), dsInfo.getRefUrls(),
+            dsInfo.isAssured(), dsInfo.getAssuredMode(),
+            dsInfo.getSafeDataLevel());
+        lsh.startHandler();
+        remoteDirectoryServers.put(lsh.getServerId(), lsh);
+      }
+    }
+  }
+
+  /**
+   * When this handler is connected to a replication server, specifies if
+   * a wanted server is connected to this replication server.
+   *
+   * @param wantedServer The server we want to know if it is connected
+   * to the replication server represented by this handler.
+   * @return boolean True is the wanted server is connected to the server
+   * represented by this handler.
+   */
+  public boolean isRemoteLDAPServer(short wantedServer)
+  {
+    synchronized (remoteDirectoryServers)
+    {
+      for (LightweightServerHandler server : remoteDirectoryServers.values())
+      {
+        if (wantedServer == server.getServerId())
+        {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  /**
+   * When the handler is connected to a replication server, specifies the
+   * replication server has remote LDAP servers connected to it.
+   *
+   * @return boolean True is the replication server has remote LDAP servers
+   * connected to it.
+   */
+  public boolean hasRemoteLDAPServers()
+  {
+    synchronized (remoteDirectoryServers)
+    {
+      return !remoteDirectoryServers.isEmpty();
+    }
+  }
+
+  /**
+   * Return a Set containing the servers known by this replicationServer.
+   * @return a set containing the servers known by this replicationServer.
+   */
+  public Set<Short> getConnectedDirectoryServerIds()
+  {
+    synchronized (remoteDirectoryServers)
+    {
+      return remoteDirectoryServers.keySet();
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getMonitorInstanceName()
+  {
+    String str = serverURL + " " + String.valueOf(serverId);
+
+    return "Connected Replication Server " + str +
+    ",cn=" + replicationServerDomain.getMonitorInstanceName();
+  }
+
+  /**
+   * Retrieves a set of attributes containing monitor data that should be
+   * returned to the client if the corresponding monitor entry is requested.
+   *
+   * @return  A set of attributes containing monitor data that should be
+   *          returned to the client if the corresponding monitor entry is
+   *          requested.
+   */
+  @Override
+  public ArrayList<Attribute> getMonitorData()
+  {
+    // Get the generic ones
+    ArrayList<Attribute> attributes = super.getMonitorData();
+
+    // Add the specific RS ones
+    attributes.add(Attributes.create("Replication-Server",
+        serverURL));
+
+    try
+    {
+      MonitorData md;
+      md = replicationServerDomain.computeMonitorData();
+
+      // Missing changes
+      long missingChanges = md.getMissingChangesRS(serverId);
+      attributes.add(Attributes.create("missing-changes", String
+          .valueOf(missingChanges)));
+
+      /* get the Server State */
+      AttributeBuilder builder = new AttributeBuilder("server-state");
+      ServerState state = md.getRSStates(serverId);
+      if (state != null)
+      {
+        for (String str : state.toStringSet())
+        {
+          builder.add(str);
+        }
+        attributes.add(builder.toAttribute());
+      }
+    }
+    catch (Exception e)
+    {
+      Message message =
+        ERR_ERROR_RETRIEVING_MONITOR_DATA.get(stackTraceToSingleLineString(e));
+      // We failed retrieving the monitor data.
+      attributes.add(Attributes.create("error", message.toString()));
+    }
+    return attributes;
+  }
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    String localString;
+    if (serverId != 0)
+    {
+      localString = "Replication Server ";
+      localString += serverId + " " + serverURL + " " + getServiceId();
+    } else
+      localString = "Unknown server";
+    return localString;
+  }
+  /**
+   * Gets the status of the connected DS.
+   * @return The status of the connected DS.
+   */
+  public ServerStatus getStatus()
+  {
+    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;
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java
index f333bd8..b9bc509 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java
@@ -85,7 +85,7 @@
      * him with assured safe data mode, we double check here that the ack sender
      * is a RS to take the ack into account.
      */
-     if (ackingServer.isLDAPserver())
+     if (ackingServer.isDataServer())
      {
        // Sanity check: this should never happen
         if (debugEnabled())
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 a64ea71..63df0d1 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
@@ -26,181 +26,217 @@
  */
 package org.opends.server.replication.server;
 
-import org.opends.messages.*;
-
-import static org.opends.server.loggers.ErrorLogger.logError;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import static org.opends.server.replication.common.StatusMachine.*;
-
-import org.opends.server.loggers.debug.DebugTracer;
 import static org.opends.messages.ReplicationMessages.*;
-import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
 
 import java.io.IOException;
-import java.util.Date;
-import java.util.List;
 import java.util.ArrayList;
-import java.util.Map;
 import java.util.Random;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+
+import org.opends.messages.Message;
 import org.opends.server.admin.std.server.MonitorProviderCfg;
-import org.opends.server.api.MonitorProvider;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.replication.common.AssuredMode;
 import org.opends.server.replication.common.ChangeNumber;
-import org.opends.server.replication.common.DSInfo;
 import org.opends.server.replication.common.RSInfo;
-import org.opends.server.replication.common.ServerState;
 import org.opends.server.replication.common.ServerStatus;
-import org.opends.server.replication.common.StatusMachine;
-import org.opends.server.replication.common.StatusMachineEvent;
-import org.opends.server.replication.protocol.*;
+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;
+import org.opends.server.replication.protocol.StartSessionMsg;
+import org.opends.server.replication.protocol.TopologyMsg;
+import org.opends.server.replication.protocol.UpdateMsg;
+import org.opends.server.replication.protocol.WindowMsg;
+import org.opends.server.replication.protocol.WindowProbeMsg;
 import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeBuilder;
 import org.opends.server.types.Attributes;
+import org.opends.server.types.DirectoryException;
 import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
 import org.opends.server.util.TimeThread;
 
 /**
- * This class defines a server handler, which handles all interaction with a
- * peer server (RS or DS).
+ * This class defines a server handler  :
+ * - that is a MessageHandler (see this class for more details)
+ * - that handles all interaction with a peer server (RS or DS).
  */
-public class ServerHandler extends MonitorProvider<MonitorProviderCfg>
+public abstract class ServerHandler extends MessageHandler
 {
-
-  /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = getTracer();
   /**
    * Time during which the server will wait for existing thread to stop
    * during the shutdownWriter.
    */
   private static final int SHUTDOWN_JOIN_TIMEOUT = 30000;
 
-  /*
-   * Properties, filled if remote server is either a DS or a RS
+  /**
+   * Close the session and log the provided error message
+   * Log nothing if message is null.
+   * @param providedSession The provided closing session.
+   * @param providedMsg     The provided error message.
+   * @param handler         The handler that manages that session.
    */
-  private short serverId;
-  private ProtocolSession session;
-  private final MsgQueue msgQueue = new MsgQueue();
-  private MsgQueue lateQueue = new MsgQueue();
-  private ReplicationServerDomain replicationServerDomain = null;
-  private String serverURL;
+  static protected void closeSession(ProtocolSession providedSession,
+      Message providedMsg, ServerHandler handler)
+  {
+    if (providedMsg != null)
+    {
+      if (debugEnabled())
+        TRACER.debugInfo("In "
+            + ((handler!=null)?handler.toString():"Replication Server")
+            + " closing session with err=" +
+            providedMsg.toString());
+      logError(providedMsg);
+    }
+    try
+    {
+      if (providedSession!=null)
+        providedSession.close();
+    } catch (IOException ee)
+    {
+      // ignore
+    }
+  }
 
-  // Number of update sent to the server
-  private int outCount = 0;
-  // Number of updates received from the server
-  private int inCount = 0;
+  /**
+   * The serverId of the remote server.
+   */
+  protected short serverId;
+  /**
+   * The session opened with the remote server.
+   */
+  protected ProtocolSession session;
 
-  // Number of updates received from the server in assured safe read mode
-  private int assuredSrReceivedUpdates = 0;
-  // Number of updates received from the server in assured safe read mode that
-  // timed out
-  private AtomicInteger assuredSrReceivedUpdatesTimeout = new AtomicInteger();
-  // Number of updates sent to the server in assured safe read mode
-  private int assuredSrSentUpdates = 0;
-  // Number of updates sent to the server in assured safe read mode that timed
-  // out
-  private AtomicInteger assuredSrSentUpdatesTimeout = new AtomicInteger();
-  // Number of updates received from the server in assured safe data mode
-  private int assuredSdReceivedUpdates = 0;
-  // Number of updates received from the server in assured safe data mode that
-  // timed out
-  private AtomicInteger assuredSdReceivedUpdatesTimeout = new AtomicInteger();
-  // Number of updates sent to the server in assured safe data mode
-  private int assuredSdSentUpdates = 0;
-  // Number of updates sent to the server in assured safe data mode that timed
-  // out
-  private AtomicInteger assuredSdSentUpdatesTimeout = new AtomicInteger();
+  /**
+   * The serverURL of the remote server.
+   */
+  protected String serverURL;
+  /**
+   * Number of updates received from the server in assured safe read mode.
+   */
+  protected int assuredSrReceivedUpdates = 0;
+  /**
+   * Number of updates received from the server in assured safe read mode that
+   * timed out.
+   */
+  protected AtomicInteger assuredSrReceivedUpdatesTimeout = new AtomicInteger();
+  /**
+   * Number of updates sent to the server in assured safe read mode.
+   */
+  protected int assuredSrSentUpdates = 0;
+  /**
+   * Number of updates sent to the server in assured safe read mode that timed
+   * out.
+   */
+  protected AtomicInteger assuredSrSentUpdatesTimeout = new AtomicInteger();
+  /**
+  // Number of updates received from the server in assured safe data mode.
+   */
+  protected int assuredSdReceivedUpdates = 0;
+  /**
+   * Number of updates received from the server in assured safe data mode that
+   * timed out.
+   */
+  protected AtomicInteger assuredSdReceivedUpdatesTimeout = new AtomicInteger();
+  /**
+   * Number of updates sent to the server in assured safe data mode.
+   */
+  protected int assuredSdSentUpdates = 0;
 
-  private int maxReceiveQueue = 0;
-  private int maxSendQueue = 0;
-  private int maxReceiveDelay = 0;
-  private int maxSendDelay = 0;
-  private int maxQueueSize = 5000;
-  private int maxQueueBytesSize = maxQueueSize * 100;
-  private int restartReceiveQueue;
-  private int restartSendQueue;
-  private int restartReceiveDelay;
-  private int restartSendDelay;
-  private boolean serverIsLDAPserver;
-  private boolean following = false;
-  private ServerState serverState;
-  private boolean activeWriter = true;
-  private ServerWriter writer = null;
-  private String baseDn = null;
+  /**
+   * Number of updates sent to the server in assured safe data mode that timed
+   * out.
+   */
+  protected AtomicInteger assuredSdSentUpdatesTimeout = new AtomicInteger();
+
+  /**
+   * The associated ServerWriter that sends messages to the remote server.
+   */
+  protected ServerReader reader;
+  /**
+   * The associated ServerReader that receives messages from the remote server.
+   */
+  protected ServerWriter writer = null;
+
+  // window
   private int rcvWindow;
   private int rcvWindowSizeHalf;
+
   private int maxRcvWindow;
-  private ServerReader reader;
-  private Semaphore sendWindow;
-  private int sendWindowSize;
-  private boolean flowControl = false; // indicate that the server is
-  // flow controlled and should
-  // be stopped from sending messages.
+  /**
+   * Semaphore that the writer uses to control the flow to the remote server.
+   */
+  protected Semaphore sendWindow;
+  /**
+   * The initial size of the sending window.
+   */
+  int sendWindowSize;
 
   private int saturationCount = 0;
-  private short replicationServerId;
-  private short protocolVersion = -1;
-  private long generationId = -1;
 
-  // Group id of this remote server
-  private byte groupId = (byte) -1;
-
-  /*
-   * Properties filled only if remote server is a DS
-   */
-
-  // Status of this DS (only used if this server handler represents a DS)
-  private ServerStatus status = ServerStatus.INVALID_STATUS;
-  // Referrals URLs this DS is exporting
-  private List<String> refUrls = new ArrayList<String>();
-  // Assured replication enabled on DS or not
-  private boolean assuredFlag = false;
-  // DS assured mode (relevant if assured replication enabled)
-  private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE;
-  // DS safe data level (relevant if assured mode is safe data)
-  private byte safeDataLevel = (byte) -1;
-
-  /*
-   * Properties filled only if remote server is a RS
-   */
-  private String serverAddressURL;
   /**
-   * When this Handler is related to a remote replication server
-   * this collection will contain as many elements as there are
-   * LDAP servers connected to the remote replication server.
+   * The protocol version established with the remote server.
    */
-  private final Map<Short, LightweightServerHandler> directoryServers =
-    new ConcurrentHashMap<Short, LightweightServerHandler>();
+  protected short protocolVersion = -1;
+  /**
+   * remote generation id.
+   */
+  protected long generationId = -1;
+  /**
+   * The generation id of the hosting RS.
+   */
+  protected long localGenerationId = -1;
+  /**
+   * The generation id before procesing a new start handshake.
+   */
+  protected long oldGenerationId = -1;
+  /**
+   * Group id of this remote server.
+   */
+  protected byte groupId = (byte) -1;
+  /**
+   * The SSL encryption provided by the creator/starter of this handler.
+   */
+  protected boolean initSslEncryption;
+
+  /**
+   * The SSL encryption after the negociation with the peer.
+   */
+  protected boolean sslEncryption;
   /**
    * The time in milliseconds between heartbeats from the replication
    * server.  Zero means heartbeats are off.
    */
-  private long heartbeatInterval = 0;
+  protected long heartbeatInterval = 0;
+
   /**
    * The thread that will send heartbeats.
    */
   HeartbeatThread heartbeatThread = null;
+
   /**
    * Set when ServerWriter is stopping.
    */
-  private boolean shutdownWriter = false;
+  protected boolean shutdownWriter = false;
+
   /**
    * Set when ServerHandler is stopping.
    */
   private AtomicBoolean shuttingDown = new AtomicBoolean(false);
 
+
   /**
    * Creates a new server handler instance with the provided socket.
    *
@@ -208,846 +244,142 @@
    *                 communicate with the remote entity.
    * @param queueSize The maximum number of update that will be kept
    *                  in memory by this ServerHandler.
+   * @param replicationServerURL The URL of the hosting replication server.
+   * @param replicationServerId The serverId of the hosting replication server.
+   * @param replicationServer The hosting replication server.
+   * @param rcvWindowSize The window size to receive from the remote server.
    */
-  public ServerHandler(ProtocolSession session, int queueSize)
+  public ServerHandler(
+      ProtocolSession session,
+      int queueSize,
+      String replicationServerURL,
+      short replicationServerId,
+      ReplicationServer replicationServer,
+      int rcvWindowSize)
   {
-    super("Server Handler");
+    super(queueSize, replicationServerURL,
+        replicationServerId, replicationServer);
     this.session = session;
-    this.maxQueueSize = queueSize;
-    this.maxQueueBytesSize = queueSize * 100;
     this.protocolVersion = ProtocolVersion.getCurrentVersion();
+    this.rcvWindowSizeHalf = rcvWindowSize / 2;
+    this.maxRcvWindow = rcvWindowSize;
+    this.rcvWindow = rcvWindowSize;
   }
 
   /**
-   * Creates a DSInfo structure representing this remote DS.
-   * @return The DSInfo structure representing this remote DS
+   * Abort a start procedure currently establishing.
+   * @param reason The provided reason.
    */
-  public DSInfo toDSInfo()
+  protected void abortStart(Message reason)
   {
-    DSInfo dsInfo = new DSInfo(serverId, replicationServerId, generationId,
-      status, assuredFlag, assuredMode, safeDataLevel, groupId, refUrls);
-
-    return dsInfo;
-  }
-
-  /**
-   * Creates a RSInfo structure representing this remote RS.
-   * @return The RSInfo structure representing this remote RS
-   */
-  public RSInfo toRSInfo()
-  {
-    RSInfo rsInfo = new RSInfo(serverId, generationId, groupId);
-
-    return rsInfo;
-  }
-
-  /**
-   * Do the handshake with either the DS or RS and then create the reader and
-   * writer thread.
-   *
-   * There are 2 possible handshake sequences: DS<->RS and RS<->RS. Each one are
-   * divided into 2 logical consecutive phases (phase 1 and phase 2):
-   *
-   * DS<->RS (DS (always initiating connection) always sends first message):
-   * -------
-   *
-   * phase 1:
-   * DS --- ServerStartMsg ---> RS
-   * DS <--- ReplServerStartMsg --- RS
-   * phase 2:
-   * DS --- StartSessionMsg ---> RS
-   * DS <--- TopologyMsg --- RS
-   *
-   * RS<->RS (RS initiating connection always sends first message):
-   * -------
-   *
-   * phase 1:
-   * RS1 --- ReplServerStartMsg ---> RS2
-   * RS1 <--- ReplServerStartMsg --- RS2
-   * phase 2:
-   * RS1 --- TopologyMsg ---> RS2
-   * RS1 <--- TopologyMsg --- RS2
-   *
-   * @param baseDn baseDn of the ServerHandler when this is an outgoing conn.
-   *               null if this is an incoming connection (listen).
-   * @param replicationServerId The identifier of the replicationServer that
-   *                            creates this server handler.
-   * @param replicationServerURL The URL of the replicationServer that creates
-   *                             this server handler.
-   * @param windowSize the window size that this server handler must use.
-   * @param sslEncryption For outgoing connections indicates whether encryption
-   *                      should be used after the exchange of start messages.
-   *                      Ignored for incoming connections.
-   * @param replicationServer the ReplicationServer that created this server
-   *                          handler.
-   */
-  public void start(String baseDn, short replicationServerId,
-    String replicationServerURL,
-    int windowSize, boolean sslEncryption,
-    ReplicationServer replicationServer)
-  {
-
-    // The handshake phase must be done by blocking any access to structures
-    // keeping info on connected servers, so that one can safely check for
-    // pre-existence of a server, send a coherent snapshot of known topology
-    // to peers, update the local view of the topology...
-    //
-    // For instance a kind of problem could be that while we connect with a
-    // peer RS, a DS is connecting at the same time and we could publish the
-    // connected DSs to the peer RS forgetting this last DS in the TopologyMsg.
-    //
-    // This method and every others that need to read/make changes to the
-    // structures holding topology for the domain should:
-    // - call ReplicationServerDomain.lock()
-    // - read/modify structures
-    // - call ReplicationServerDomain.release()
-    //
-    // More information is provided in comment of ReplicationServerDomain.lock()
-
-    // If domain already exists, lock it until handshake is finished otherwise
-    // it will be created and locked later in the method
-    if (baseDn != null)
+    // We did not recognize the message, close session as what
+    // can happen after is undetermined and we do not want the server to
+    // be disturbed
+    if (session!=null)
     {
-      ReplicationServerDomain rsd =
-        replicationServer.getReplicationServerDomain(baseDn, false);
-      if (rsd != null)
+      try
       {
-        try
-        {
-          rsd.lock();
-        } catch (InterruptedException ex)
-        {
-          // Thread interrupted, return.
-          return;
-        }
+        session.publish(
+            new ErrorMsg(
+                replicationServerDomain.getReplicationServer().getServerId(),
+                serverId,
+                reason));
       }
+      catch(Exception e)
+      {
+      }
+      closeSession(session, reason, this);
     }
 
-    long oldGenerationId = -100;
+    if ((replicationServerDomain != null) &&
+        replicationServerDomain.hasLock())
+      replicationServerDomain.release();
 
-    if (debugEnabled())
-      TRACER.debugInfo("In " + replicationServer.getMonitorInstanceName() +
-        " starts a new LS or RS " +
-        ((baseDn == null) ? "incoming connection" : "outgoing connection"));
-
-    this.replicationServerId = replicationServerId;
-    rcvWindowSizeHalf = windowSize / 2;
-    maxRcvWindow = windowSize;
-    rcvWindow = windowSize;
-    long localGenerationId = -1;
-    ReplServerStartMsg outReplServerStartMsg = null;
-
-    /**
-     * This boolean prevents from logging a polluting error when connection\
-     * aborted from a DS that wanted only to perform handshake phase 1 in order
-     * to determine the best suitable RS:
-     * 1) -> ServerStartMsg
-     * 2) <- ReplServerStartMsg
-     * 3) connection closure
-     */
-    boolean log_error_message = true;
-
-    try
+    // If generation id of domain was changed, set it back to old value
+    // We may have changed it as it was -1 and we received a value >0 from
+    // peer server and the last topo message sent may have failed being
+    // sent: in that case retrieve old value of generation id for
+    // replication server domain
+    if (oldGenerationId != -100)
     {
-      /*
-       * PROCEDE WITH FIRST PHASE OF HANDSHAKE:
-       * ServerStartMsg then ReplServerStartMsg (with a DS)
-       * OR
-       * ReplServerStartMsg then ReplServerStartMsg (with a RS)
-       */
+      replicationServerDomain.setGenerationId(oldGenerationId, false);
+    }
+  }
 
-      if (baseDn != null) // Outgoing connection
-
+  /**
+   * Check the protocol window and send WindowMsg if necessary.
+   *
+   * @throws IOException when the session becomes unavailable.
+   */
+  public synchronized void checkWindow() throws IOException
+  {
+    if (rcvWindow < rcvWindowSizeHalf)
+    {
+      if (flowControl)
       {
-        // This is an outgoing connection. Publish our start message.
-        this.baseDn = baseDn;
-
-        // Get or create the ReplicationServerDomain
-        replicationServerDomain =
-          replicationServer.getReplicationServerDomain(baseDn, true);
-        if (!replicationServerDomain.hasLock())
+        if (replicationServerDomain.restartAfterSaturation(this))
         {
-          try
-          {
-            replicationServerDomain.lock();
-          } catch (InterruptedException ex)
-          {
-            // Thread interrupted, return.
-            return;
-          }
+          flowControl = false;
         }
-        localGenerationId = replicationServerDomain.getGenerationId();
+      }
+      if (!flowControl)
+      {
+        WindowMsg msg = new WindowMsg(rcvWindowSizeHalf);
+        session.publish(msg);
+        rcvWindow += rcvWindowSizeHalf;
+      }
+    }
+  }
 
-        ServerState localServerState =
-          replicationServerDomain.getDbServerState();
-        outReplServerStartMsg = new ReplServerStartMsg(replicationServerId,
-          replicationServerURL,
-          baseDn, windowSize, localServerState,
-          protocolVersion, localGenerationId,
-          sslEncryption,
-          replicationServer.getGroupId(),
-          replicationServerDomain.
-          getReplicationServer().getDegradedStatusThreshold());
+  /**
+   * Decrement the protocol window, then check if it is necessary
+   * to send a WindowMsg and send it.
+   *
+   * @throws IOException when the session becomes unavailable.
+   */
+  public synchronized void decAndCheckWindow() throws IOException
+  {
+    rcvWindow--;
+    checkWindow();
+  }
 
-        session.publish(outReplServerStartMsg);
+  /**
+   * Set the shut down flag to true and returns the previous value of the flag.
+   * @return The previous value of the shut down flag
+   */
+  public boolean engageShutdown()
+  {
+    // Use thread safe boolean
+    return shuttingDown.getAndSet(true);
+  }
+
+  /**
+   * Finalize the initialization, create reader, writer, heartbeat system
+   * and monitoring system.
+   * @throws DirectoryException When an exception is raised.
+   */
+  protected void finalizeStart()
+  throws DirectoryException
+  {
+    // FIXME:ECL We should refactor so that a SH always have a session
+    if (session != null)
+    {
+      try
+      {
+        // Disable timeout for next communications
+        session.setSoTimeout(0);
+      }
+      catch(Exception e)
+      {
       }
 
-      // Wait and process ServerStartMsg or ReplServerStartMsg
-      ReplicationMsg msg = session.receive();
-      if (msg instanceof ServerStartMsg)
-      {
-        // The remote server is an LDAP Server.
-        ServerStartMsg serverStartMsg = (ServerStartMsg) msg;
-
-        generationId = serverStartMsg.getGenerationId();
-        protocolVersion = ProtocolVersion.minWithCurrent(
-          serverStartMsg.getVersion());
-        serverId = serverStartMsg.getServerId();
-        serverURL = serverStartMsg.getServerURL();
-        this.baseDn = serverStartMsg.getBaseDn();
-        this.serverState = serverStartMsg.getServerState();
-        this.groupId = serverStartMsg.getGroupId();
-
-        maxReceiveDelay = serverStartMsg.getMaxReceiveDelay();
-        maxReceiveQueue = serverStartMsg.getMaxReceiveQueue();
-        maxSendDelay = serverStartMsg.getMaxSendDelay();
-        maxSendQueue = serverStartMsg.getMaxSendQueue();
-        heartbeatInterval = serverStartMsg.getHeartbeatInterval();
-
-        // The session initiator decides whether to use SSL.
-        sslEncryption = serverStartMsg.getSSLEncryption();
-
-        if (maxReceiveQueue > 0)
-          restartReceiveQueue = (maxReceiveQueue > 1000 ? maxReceiveQueue -
-            200 : maxReceiveQueue * 8 / 10);
-        else
-          restartReceiveQueue = 0;
-
-        if (maxSendQueue > 0)
-          restartSendQueue =
-            (maxSendQueue > 1000 ? maxSendQueue - 200 : maxSendQueue * 8 /
-            10);
-        else
-          restartSendQueue = 0;
-
-        if (maxReceiveDelay > 0)
-          restartReceiveDelay = (maxReceiveDelay > 10 ? maxReceiveDelay - 1
-            : maxReceiveDelay);
-        else
-          restartReceiveDelay = 0;
-
-        if (maxSendDelay > 0)
-          restartSendDelay =
-            (maxSendDelay > 10 ? maxSendDelay - 1 : maxSendDelay);
-        else
-          restartSendDelay = 0;
-
-        if (heartbeatInterval < 0)
-        {
-          heartbeatInterval = 0;
-        }
-
-        serverIsLDAPserver = true;
-
-        // Get or Create the ReplicationServerDomain
-        replicationServerDomain =
-          replicationServer.getReplicationServerDomain(this.baseDn, true);
-
-        // Hack to be sure that if a server disconnects and reconnect, we
-        // let the reader thread see the closure and cleanup any reference
-        // to old connection
-        replicationServerDomain.waitDisconnection(serverStartMsg.getServerId());
-
-        if (!replicationServerDomain.hasLock())
-        {
-          try
-          {
-            replicationServerDomain.lock();
-          } catch (InterruptedException ex)
-          {
-            // Thread interrupted, return.
-            return;
-          }
-        }
-
-        // Duplicate server ?
-        if (!replicationServerDomain.checkForDuplicateDS(this))
-        {
-          closeSession(null);
-          if ((replicationServerDomain != null) &&
-            replicationServerDomain.hasLock())
-            replicationServerDomain.release();
-          return;
-        }
-
-        localGenerationId = replicationServerDomain.getGenerationId();
-
-        ServerState localServerState =
-          replicationServerDomain.getDbServerState();
-        // This an incoming connection. Publish our start message
-        ReplServerStartMsg replServerStartMsg =
-          new ReplServerStartMsg(replicationServerId, replicationServerURL,
-          this.baseDn, windowSize, localServerState,
-          protocolVersion, localGenerationId,
-          sslEncryption,
-          replicationServer.getGroupId(),
-          replicationServerDomain.
-          getReplicationServer().getDegradedStatusThreshold());
-        session.publish(replServerStartMsg);
-        sendWindowSize = serverStartMsg.getWindowSize();
-
-        /* Until here session is encrypted then it depends on the
-        negotiation */
-        if (!sslEncryption)
-        {
-          session.stopEncryption();
-        }
-
-        if (debugEnabled())
-        {
-          TRACER.debugInfo("In " +
-            replicationServerDomain.getReplicationServer().
-            getMonitorInstanceName() + ":" +
-            "\nSH HANDSHAKE RECEIVED:\n" + serverStartMsg.toString() +
-            "\nAND REPLIED:\n" + replServerStartMsg.toString());
-        }
-      } else if (msg instanceof ReplServerStartMsg)
-      {
-        // The remote server is a replication server
-        ReplServerStartMsg inReplServerStartMsg = (ReplServerStartMsg) msg;
-        protocolVersion = ProtocolVersion.minWithCurrent(
-          inReplServerStartMsg.getVersion());
-        generationId = inReplServerStartMsg.getGenerationId();
-        serverId = inReplServerStartMsg.getServerId();
-        serverURL = inReplServerStartMsg.getServerURL();
-        int separator = serverURL.lastIndexOf(':');
-        serverAddressURL =
-          session.getRemoteAddress() + ":" + serverURL.substring(separator +
-          1);
-        serverIsLDAPserver = false;
-        if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
-        {
-          // We support connection from a V1 RS
-          // Only V2 protocol has the group id in repl server start message
-          this.groupId = inReplServerStartMsg.getGroupId();
-        }
-        this.baseDn = inReplServerStartMsg.getBaseDn();
-
-        if (baseDn == null) // Reply to incoming RS
-
-        {
-          // Get or create the ReplicationServerDomain
-          replicationServerDomain =
-            replicationServer.getReplicationServerDomain(this.baseDn, true);
-          if (!replicationServerDomain.hasLock())
-          {
-            try
-            {
-              /**
-               * Take the lock on the domain.
-               * WARNING: Here we try to acquire the lock with a timeout. This
-               * is for preventing a deadlock that may happen if there are cross
-               * connection attempts (for same domain) from this replication
-               * server and from a peer one:
-               * Here is the scenario:
-               * - RS1 connect thread takes the domain lock and starts
-               * connection to RS2
-               * - at the same time RS2 connect thread takes his domain lock and
-               * start connection to RS2
-               * - RS2 listen thread starts processing received
-               * ReplServerStartMsg from RS1 and wants to acquire the lock on
-               * the domain (here) but cannot as RS2 connect thread already has
-               * it
-               * - RS1 listen thread starts processing received
-               * ReplServerStartMsg from RS2 and wants to acquire the lock on
-               * the domain (here) but cannot as RS1 connect thread already has
-               * it
-               * => Deadlock: 4 threads are locked.
-               * So to prevent that in such situation, the listen threads here
-               * will both timeout trying to acquire the lock. The random time
-               * for the timeout should allow on connection attempt to be
-               * aborted whereas the other one should have time to finish in the
-               * same time.
-               * Warning: the minimum time (3s) should be big enough to allow
-               * normal situation connections to terminate. The added random
-               * time should represent a big enough range so that the chance to
-               * have one listen thread timing out a lot before the peer one is
-               * great. When the first listen thread times out, the remote
-               * connect thread should release the lock and allow the peer
-               * listen thread to take the lock it was waiting for and process
-               * the connection attempt.
-               */
-              Random random = new Random();
-              int randomTime = random.nextInt(6); // Random from 0 to 5
-              // Wait at least 3 seconds + (0 to 5 seconds)
-              long timeout = (long) (3000 + ( randomTime * 1000 ) );
-              boolean noTimeout = replicationServerDomain.tryLock(timeout);
-              if (!noTimeout)
-              {
-                // Timeout
-                Message message = NOTE_TIMEOUT_WHEN_CROSS_CONNECTION.get(
-                  this.baseDn,
-                  Short.toString(serverId),
-                  Short.toString(replicationServer.getServerId()));
-                closeSession(message);
-                return;
-              }
-            } catch (InterruptedException ex)
-            {
-              // Thread interrupted, return.
-              return;
-            }
-          }
-          localGenerationId = replicationServerDomain.getGenerationId();
-          ServerState domServerState =
-            replicationServerDomain.getDbServerState();
-
-          // The session initiator decides whether to use SSL.
-          sslEncryption = inReplServerStartMsg.getSSLEncryption();
-
-          // Publish our start message
-          outReplServerStartMsg = new ReplServerStartMsg(replicationServerId,
-            replicationServerURL,
-            this.baseDn, windowSize, domServerState,
-            protocolVersion,
-            localGenerationId,
-            sslEncryption,
-            replicationServer.getGroupId(),
-            replicationServerDomain.
-            getReplicationServer().getDegradedStatusThreshold());
-
-          if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
-          {
-            session.publish(outReplServerStartMsg);
-          } else {
-            // We support connection from a V1 RS, send PDU with V1 form
-            session.publish(outReplServerStartMsg,
-              ProtocolVersion.REPLICATION_PROTOCOL_V1);
-          }
-
-          if (debugEnabled())
-          {
-            TRACER.debugInfo("In " +
-              replicationServerDomain.getReplicationServer().
-              getMonitorInstanceName() + ":" +
-              "\nSH HANDSHAKE RECEIVED:\n" + inReplServerStartMsg.toString() +
-              "\nAND REPLIED:\n" + outReplServerStartMsg.toString());
-          }
-        } else
-        {
-          // Did the remote RS answer with the DN we provided him ?
-          if (!(this.baseDn.equals(baseDn)))
-          {
-            Message message = ERR_RS_DN_DOES_NOT_MATCH.get(
-              this.baseDn.toString(),
-              baseDn.toString());
-            closeSession(message);
-            if ((replicationServerDomain != null) &&
-              replicationServerDomain.hasLock())
-              replicationServerDomain.release();
-            return;
-          }
-
-          if (debugEnabled())
-          {
-            TRACER.debugInfo("In " +
-              replicationServerDomain.getReplicationServer().
-              getMonitorInstanceName() + ":" +
-              "\nSH HANDSHAKE SENT:\n" + outReplServerStartMsg.toString() +
-              "\nAND RECEIVED:\n" + inReplServerStartMsg.toString());
-          }
-        }
-        this.serverState = inReplServerStartMsg.getServerState();
-        sendWindowSize = inReplServerStartMsg.getWindowSize();
-
-        // Duplicate server ?
-        if (!replicationServerDomain.checkForDuplicateRS(this))
-        {
-          closeSession(null);
-          if ((replicationServerDomain != null) &&
-            replicationServerDomain.hasLock())
-            replicationServerDomain.release();
-          return;
-        }
-
-        /* Until here session is encrypted then it depends on the
-        negociation */
-        if (!sslEncryption)
-        {
-          session.stopEncryption();
-        }
-      } else
-      {
-        // We did not recognize the message, close session as what
-        // can happen after is undetermined and we do not want the server to
-        // be disturbed
-        closeSession(null);
-        if ((replicationServerDomain != null) &&
-          replicationServerDomain.hasLock())
-          replicationServerDomain.release();
-        return;
-      }
-
-      if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
-      { // Only protocol version above V1 has a phase 2 handshake
-
-        /*
-         * NOW PROCEDE WITH SECOND PHASE OF HANDSHAKE:
-         * TopologyMsg then TopologyMsg (with a RS)
-         * OR
-         * StartSessionMsg then TopologyMsg (with a DS)
-         */
-
-        TopologyMsg outTopoMsg = null;
-
-        if (baseDn != null) // Outgoing connection to a RS
-
-        {
-          // Send our own TopologyMsg to remote RS
-          outTopoMsg = replicationServerDomain.createTopologyMsgForRS();
-          session.publish(outTopoMsg);
-        }
-
-        // Wait and process TopologyMsg or StartSessionMsg
-        log_error_message = false;
-        ReplicationMsg msg2 = session.receive();
-        log_error_message = true;
-        if (msg2 instanceof TopologyMsg)
-        {
-          // Remote RS sent his topo msg
-          TopologyMsg inTopoMsg = (TopologyMsg) msg2;
-
-          // CONNECTION WITH A RS
-
-          // if the remote RS and the local RS have the same genID
-          // then it's ok and nothing else to do
-          if (generationId == localGenerationId)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugInfo("In " +
-                replicationServerDomain.getReplicationServer().
-                getMonitorInstanceName() + " RS with serverID=" + serverId +
-                " is connected with the right generation ID");
-            }
-          } else
-          {
-            if (localGenerationId > 0)
-            {
-              // if the local RS is initialized
-              if (generationId > 0)
-              {
-                // if the remote RS is initialized
-                if (generationId != localGenerationId)
-                {
-                  // if the 2 RS have different generationID
-                  if (replicationServerDomain.getGenerationIdSavedStatus())
-                  {
-                    // if the present RS has received changes regarding its
-                    //     gen ID and so won't change without a reset
-                    // then  we are just degrading the peer.
-                    Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
-                      this.baseDn,
-                      Short.toString(serverId),
-                      Long.toString(generationId),
-                      Long.toString(localGenerationId));
-                    logError(message);
-                  } else
-                  {
-                    // The present RS has never received changes regarding its
-                    // gen ID.
-                    //
-                    // Example case:
-                    // - we are in RS1
-                    // - RS2 has genId2 from LS2 (genId2 <=> no data in LS2)
-                    // - RS1 has genId1 from LS1 /genId1 comes from data in
-                    //   suffix
-                    // - we are in RS1 and we receive a START msg from RS2
-                    // - Each RS keeps its genID / is degraded and when LS2
-                    //   will be populated from LS1 everything will become ok.
-                    //
-                    // Issue:
-                    // FIXME : Would it be a good idea in some cases to just
-                    //         set the gen ID received from the peer RS
-                    //         specially if the peer has a non null state and
-                    //         we have a nul state ?
-                    // replicationServerDomain.
-                    // setGenerationId(generationId, false);
-                    Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
-                      this.baseDn,
-                      Short.toString(serverId),
-                      Long.toString(generationId),
-                      Long.toString(localGenerationId));
-                    logError(message);
-                  }
-                }
-              } else
-              {
-                // The remote RS has no genId. We don't change anything for the
-                // current RS.
-              }
-            } else
-            {
-              // The local RS is not initialized - take the one received
-              // WARNING: Must be done before computing topo message to send
-              // to peer server as topo message must embed valid generation id
-              // for our server
-              oldGenerationId =
-                replicationServerDomain.setGenerationId(generationId, false);
-            }
-          }
-
-          if (baseDn == null) // Reply to the RS (incoming connection)
-
-          {
-            // Send our own TopologyMsg to remote RS
-            outTopoMsg = replicationServerDomain.createTopologyMsgForRS();
-            session.publish(outTopoMsg);
-
-            if (debugEnabled())
-            {
-              TRACER.debugInfo("In " +
-                replicationServerDomain.getReplicationServer().
-                getMonitorInstanceName() + ":" +
-                "\nSH HANDSHAKE RECEIVED:\n" + inTopoMsg.toString() +
-                "\nAND REPLIED:\n" + outTopoMsg.toString());
-            }
-          } else
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugInfo("In " +
-                replicationServerDomain.getReplicationServer().
-                getMonitorInstanceName() + ":" +
-                "\nSH HANDSHAKE SENT:\n" + outTopoMsg.toString() +
-                "\nAND RECEIVED:\n" + inTopoMsg.toString());
-            }
-          }
-
-          // Alright, connected with new RS (either outgoing or incoming
-          // connection): store handler.
-          Map<Short, ServerHandler> connectedRSs =
-            replicationServerDomain.getConnectedRSs();
-          connectedRSs.put(serverId, this);
-
-          // Process TopologyMsg sent by remote RS: store matching new info
-          // (this will also warn our connected DSs of the new received info)
-          replicationServerDomain.receiveTopoInfoFromRS(inTopoMsg, this, false);
-
-        } else if (msg2 instanceof StartSessionMsg)
-        {
-          // CONNECTION WITH A DS
-
-          // Process StartSessionMsg sent by remote DS
-          StartSessionMsg startSessionMsg = (StartSessionMsg) msg2;
-
-          this.status = startSessionMsg.getStatus();
-          // Sanity check: is it a valid initial status?
-          if (!isValidInitialStatus(this.status))
-          {
-            Message mesg = ERR_RS_INVALID_INIT_STATUS.get(
-              this.status.toString(), this.baseDn.toString(),
-              Short.toString(serverId));
-            closeSession(mesg);
-            if ((replicationServerDomain != null) &&
-              replicationServerDomain.hasLock())
-              replicationServerDomain.release();
-            return;
-          }
-          this.refUrls = startSessionMsg.getReferralsURLs();
-          this.assuredFlag = startSessionMsg.isAssured();
-          this.assuredMode = startSessionMsg.getAssuredMode();
-          this.safeDataLevel = startSessionMsg.getSafeDataLevel();
-
-          /*
-           * If we have already a generationID set for the domain
-           * then
-           *   if the connecting replica has not the same
-           *   then it is degraded locally and notified by an error message
-           * else
-           *   we set the generationID from the one received
-           *   (unsaved yet on disk . will be set with the 1rst change
-           * received)
-           */
-          if (localGenerationId > 0)
-          {
-            if (generationId != localGenerationId)
-            {
-              Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get(
-                this.baseDn,
-                Short.toString(serverId),
-                Long.toString(generationId),
-                Long.toString(localGenerationId));
-              logError(message);
-            }
-          } else
-          {
-            // We are an empty Replicationserver
-            if ((generationId > 0) && (!serverState.isEmpty()))
-            {
-              // If the LDAP server has already sent changes
-              // it is not expected to connect to an empty RS
-              Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get(
-                this.baseDn,
-                Short.toString(serverId),
-                Long.toString(generationId),
-                Long.toString(localGenerationId));
-              logError(message);
-            } else
-            {
-              // The local RS is not initialized - take the one received
-              // WARNING: Must be done before computing topo message to send
-              // to peer server as topo message must embed valid generation id
-              // for our server
-              oldGenerationId =
-                replicationServerDomain.setGenerationId(generationId, false);
-            }
-          }
-
-          // Send our own TopologyMsg to DS
-          outTopoMsg = replicationServerDomain.createTopologyMsgForDS(
-            this.serverId);
-          session.publish(outTopoMsg);
-
-          if (debugEnabled())
-          {
-            TRACER.debugInfo("In " +
-              replicationServerDomain.getReplicationServer().
-              getMonitorInstanceName() + ":" +
-              "\nSH HANDSHAKE RECEIVED:\n" + startSessionMsg.toString() +
-              "\nAND REPLIED:\n" + outTopoMsg.toString());
-          }
-
-          // Alright, connected with new DS: store handler.
-          Map<Short, ServerHandler> connectedDSs =
-            replicationServerDomain.getConnectedDSs();
-          connectedDSs.put(serverId, this);
-
-          // Tell peer DSs a new DS just connected to us
-          // No need to resend topo msg to this just new DS so not null
-          // argument
-          replicationServerDomain.sendTopoInfoToDSs(this);
-          // Tell peer RSs a new DS just connected to us
-          replicationServerDomain.sendTopoInfoToRSs();
-        } else
-        {
-          // We did not recognize the message, close session as what
-          // can happen after is undetermined and we do not want the server to
-          // be disturbed
-          closeSession(null);
-          if ((replicationServerDomain != null) &&
-            replicationServerDomain.hasLock())
-            replicationServerDomain.release();
-          return;
-        }
-      } else
-      {
-        // Terminate connection from a V1 RS
-
-        // if the remote RS and the local RS have the same genID
-        // then it's ok and nothing else to do
-        if (generationId == localGenerationId)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugInfo("In " +
-              replicationServerDomain.getReplicationServer().
-              getMonitorInstanceName() + " RS V1 with serverID=" + serverId +
-              " is connected with the right generation ID");
-          }
-        } else
-        {
-          if (localGenerationId > 0)
-          {
-            // if the local RS is initialized
-            if (generationId > 0)
-            {
-              // if the remote RS is initialized
-              if (generationId != localGenerationId)
-              {
-                // if the 2 RS have different generationID
-                if (replicationServerDomain.getGenerationIdSavedStatus())
-                {
-                  // if the present RS has received changes regarding its
-                  //     gen ID and so won't change without a reset
-                  // then  we are just degrading the peer.
-                  Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
-                    this.baseDn,
-                    Short.toString(serverId),
-                    Long.toString(generationId),
-                    Long.toString(localGenerationId));
-                  logError(message);
-                } else
-                {
-                  // The present RS has never received changes regarding its
-                  // gen ID.
-                  //
-                  // Example case:
-                  // - we are in RS1
-                  // - RS2 has genId2 from LS2 (genId2 <=> no data in LS2)
-                  // - RS1 has genId1 from LS1 /genId1 comes from data in
-                  //   suffix
-                  // - we are in RS1 and we receive a START msg from RS2
-                  // - Each RS keeps its genID / is degraded and when LS2
-                  //   will be populated from LS1 everything will become ok.
-                  //
-                  // Issue:
-                  // FIXME : Would it be a good idea in some cases to just
-                  //         set the gen ID received from the peer RS
-                  //         specially if the peer has a non null state and
-                  //         we have a nul state ?
-                  // replicationServerDomain.
-                  // setGenerationId(generationId, false);
-                  Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
-                    this.baseDn,
-                    Short.toString(serverId),
-                    Long.toString(generationId),
-                    Long.toString(localGenerationId));
-                  logError(message);
-                }
-              }
-            } else
-            {
-              // The remote RS has no genId. We don't change anything for the
-              // current RS.
-            }
-          } else
-          {
-            // The local RS is not initialized - take the one received
-            oldGenerationId =
-              replicationServerDomain.setGenerationId(generationId, false);
-          }
-        }
-
-        // Alright, connected with new incoming V1 RS: store handler.
-        Map<Short, ServerHandler> connectedRSs =
-          replicationServerDomain.getConnectedRSs();
-        connectedRSs.put(serverId, this);
-
-        // Note: the supported scenario for V1->V2 upgrade is to upgrade 1 by 1
-        // all the servers of the topology. We prefer not not send a TopologyMsg
-        // for giving partial/false information to the V2 servers as for
-        // instance we don't have the connected DS of the V1 RS...When the V1
-        // RS will be upgraded in his turn, topo info will be sent and accurate.
-        // That way, there is  no risk to have false/incomplete information in
-        // other servers.
-      }
-
-      /*
-       * FINALIZE INITIALIZATION:
-       * CREATE READER AND WRITER, HEARTBEAT SYSTEM AND UPDATE MONITORING
-       * SYSTEM
-       */
-
-      // Disable timeout for next communications
-      session.setSoTimeout(0);
       // sendWindow MUST be created before starting the writer
       sendWindow = new Semaphore(sendWindowSize);
 
       writer = new ServerWriter(session, serverId,
-        this, replicationServerDomain);
+          this, replicationServerDomain);
       reader = new ServerReader(session, serverId,
-        this, replicationServerDomain);
+          this, replicationServerDomain);
 
       reader.start();
       writer.start();
@@ -1056,440 +388,30 @@
       if (heartbeatInterval > 0)
       {
         heartbeatThread = new HeartbeatThread(
-          "Replication Heartbeat to DS " + serverURL + " " + serverId +
-          " for " + this.baseDn + " in RS " + replicationServerId,
-          session, heartbeatInterval / 3);
+            "Replication Heartbeat to " + this +
+            " in RS " + replicationServerDomain.getReplicationServer().
+            getMonitorInstanceName(),
+            session, heartbeatInterval / 3);
         heartbeatThread.start();
       }
-
-      // Create the status analyzer for the domain if not already started
-      if (serverIsLDAPserver)
-      {
-        if (!replicationServerDomain.isRunningStatusAnalyzer())
-        {
-          if (debugEnabled())
-            TRACER.debugInfo("In " + replicationServerDomain.
-              getReplicationServer().
-              getMonitorInstanceName() +
-              " SH for remote server " + this.getMonitorInstanceName() +
-              " is starting status analyzer");
-          replicationServerDomain.startStatusAnalyzer();
-        }
-      }
-
-      DirectoryServer.deregisterMonitorProvider(getMonitorInstanceName());
-      DirectoryServer.registerMonitorProvider(this);
-    } catch (NotSupportedOldVersionPDUException e)
-    {
-      // We do not need to support DS V1 connection, we just accept RS V1
-      // connection:
-      // We just trash the message, log the event for debug purpose and close
-      // the connection
-      if (debugEnabled())
-      TRACER.debugInfo("In " + replicationServer.getMonitorInstanceName() + ":"
-        + e.getMessage());
-      closeSession(null);
-    } catch (Exception e)
-    {
-      // We do not want polluting error log if error is due to normal session
-      // aborted after handshake phase one from a DS that is searching for best
-      // suitable RS.
-      if ( log_error_message || (baseDn != null) )
-      {
-        // some problem happened, reject the connection
-        MessageBuilder mb = new MessageBuilder();
-        mb.append(ERR_REPLICATION_SERVER_CONNECTION_ERROR.get(
-          this.getMonitorInstanceName()));
-        mb.append(": " + stackTraceToSingleLineString(e));
-        closeSession(mb.toMessage());
-      } else
-      {
-        closeSession(null);
-      }
-
-      // If generation id of domain was changed, set it back to old value
-      // We may have changed it as it was -1 and we received a value >0 from
-      // peer server and the last topo message sent may have failed being
-      // sent: in that case retrieve old value of generation id for
-      // replication server domain
-      if (oldGenerationId != -100)
-      {
-        replicationServerDomain.setGenerationId(oldGenerationId, false);
-      }
     }
 
-    // Release domain
-    if ((replicationServerDomain != null) &&
-      replicationServerDomain.hasLock())
-      replicationServerDomain.release();
-  }
-
-  /*
-   * Close the session logging the passed error message
-   * Log nothing if message is null.
-   */
-  private void closeSession(Message msg)
-  {
-    if (msg != null)
-    {
-      logError(msg);
-    }
-    try
-    {
-      session.close();
-    } catch (IOException ee)
-    {
-      // ignore
-    }
+    DirectoryServer.deregisterMonitorProvider(getMonitorInstanceName());
+    DirectoryServer.registerMonitorProvider(this);
   }
 
   /**
-   * get the Server Id.
+   * Sends a message containing a generationId to a peer server.
+   * The peer is expected to be a replication server.
    *
-   * @return the ID of the server to which this object is linked
-   */
-  public short getServerId()
-  {
-    return serverId;
-  }
-
-  /**
-   * Retrieves the Address URL for this server handler.
+   * @param  msg         The GenerationIdMessage message to be sent.
+   * @throws IOException When it occurs while sending the message,
    *
-   * @return  The Address URL for this server handler,
-   *          in the form of an IP address and port separated by a colon.
    */
-  public String getServerAddressURL()
+  public void forwardGenerationIdToRS(ResetGenerationIdMsg msg)
+  throws IOException
   {
-    return serverAddressURL;
-  }
-
-  /**
-   * Retrieves the URL for this server handler.
-   *
-   * @return  The URL for this server handler, in the form of an address and
-   *          port separated by a colon.
-   */
-  public String getServerURL()
-  {
-    return serverURL;
-  }
-
-  /**
-   * Increase the counter of updates sent to the server.
-   */
-  public void incrementOutCount()
-  {
-    outCount++;
-  }
-
-  /**
-   * Increase the counter of update received from the server.
-   */
-  public void incrementInCount()
-  {
-    inCount++;
-  }
-
-  /**
-   * Get the count of updates received from the server.
-   * @return the count of update received from the server.
-   */
-  public int getInCount()
-  {
-    return inCount;
-  }
-
-  /**
-   * Get the count of updates sent to this server.
-   * @return  The count of update sent to this server.
-   */
-  public int getOutCount()
-  {
-    return outCount;
-  }
-
-  /**
-   * Get the number of updates received from the server in assured safe read
-   * mode.
-   * @return The number of updates received from the server in assured safe read
-   * mode
-   */
-  public int getAssuredSrReceivedUpdates()
-  {
-    return assuredSrReceivedUpdates;
-  }
-
-  /**
-   * Get the number of updates received from the server in assured safe read
-   * mode that timed out.
-   * @return The number of updates received from the server in assured safe read
-   * mode that timed out.
-   */
-  public AtomicInteger getAssuredSrReceivedUpdatesTimeout()
-  {
-    return assuredSrReceivedUpdatesTimeout;
-  }
-
-  /**
-   * Get the number of updates sent to the server in assured safe read mode.
-   * @return The number of updates sent to the server in assured safe read mode
-   */
-  public int getAssuredSrSentUpdates()
-  {
-    return assuredSrSentUpdates;
-  }
-
-  /**
-   * Get the number of updates sent to the server in assured safe read mode that
-   * timed out.
-   * @return The number of updates sent to the server in assured safe read mode
-   * that timed out.
-   */
-  public AtomicInteger getAssuredSrSentUpdatesTimeout()
-  {
-    return assuredSrSentUpdatesTimeout;
-  }
-
-    /**
-   * Get the number of updates received from the server in assured safe data
-   * mode.
-   * @return The number of updates received from the server in assured safe data
-   * mode
-   */
-  public int getAssuredSdReceivedUpdates()
-  {
-    return assuredSdReceivedUpdates;
-  }
-
-  /**
-   * Get the number of updates received from the server in assured safe data
-   * mode that timed out.
-   * @return The number of updates received from the server in assured safe data
-   * mode that timed out.
-   */
-  public AtomicInteger getAssuredSdReceivedUpdatesTimeout()
-  {
-    return assuredSdReceivedUpdatesTimeout;
-  }
-
-  /**
-   * Get the number of updates sent to the server in assured safe data mode.
-   * @return The number of updates sent to the server in assured safe data mode
-   */
-  public int getAssuredSdSentUpdates()
-  {
-    return assuredSdSentUpdates;
-  }
-
-  /**
-   * Get the number of updates sent to the server in assured safe data mode that
-   * timed out.
-   * @return The number of updates sent to the server in assured safe data mode
-   * that timed out.
-   */
-  public AtomicInteger getAssuredSdSentUpdatesTimeout()
-  {
-    return assuredSdSentUpdatesTimeout;
-  }
-
-  /**
-   * Increment the number of updates received from the server in assured safe
-   * read mode.
-   */
-  public void incrementAssuredSrReceivedUpdates()
-  {
-    assuredSrReceivedUpdates++;
-  }
-
-  /**
-   * Increment the number of updates received from the server in assured safe
-   * read mode that timed out.
-   */
-  public void incrementAssuredSrReceivedUpdatesTimeout()
-  {
-    assuredSrReceivedUpdatesTimeout.incrementAndGet();
-  }
-
-  /**
-   * Increment the number of updates sent to the server in assured safe read
-   * mode.
-   */
-  public void incrementAssuredSrSentUpdates()
-  {
-    assuredSrSentUpdates++;
-  }
-
-  /**
-   * Increment the number of updates sent to the server in assured safe read
-   * mode that timed out.
-   */
-  public void incrementAssuredSrSentUpdatesTimeout()
-  {
-    assuredSrSentUpdatesTimeout.incrementAndGet();
-  }
-
-  /**
-   * Increment the number of updates received from the server in assured safe
-   * data mode.
-   */
-  public void incrementAssuredSdReceivedUpdates()
-  {
-    assuredSdReceivedUpdates++;
-  }
-
-  /**
-   * Increment the number of updates received from the server in assured safe
-   * data mode that timed out.
-   */
-  public void incrementAssuredSdReceivedUpdatesTimeout()
-  {
-    assuredSdReceivedUpdatesTimeout.incrementAndGet();
-  }
-
-  /**
-   * Increment the number of updates sent to the server in assured safe data
-   * mode.
-   */
-  public void incrementAssuredSdSentUpdates()
-  {
-    assuredSdSentUpdates++;
-  }
-
-  /**
-   * Increment the number of updates sent to the server in assured safe data
-   * mode that timed out.
-   */
-  public void incrementAssuredSdSentUpdatesTimeout()
-  {
-    assuredSdSentUpdatesTimeout.incrementAndGet();
-  }
-
-  /**
-   * Check is this server is saturated (this server has already been
-   * sent a bunch of updates and has not processed them so they are staying
-   * in the message queue for this server an the size of the queue
-   * for this server is above the configured limit.
-   *
-   * The limit can be defined in number of updates or with a maximum delay
-   *
-   * @param changeNumber The changenumber to use to make the delay calculations.
-   * @param sourceHandler The ServerHandler which is sending the update.
-   * @return true is saturated false if not saturated.
-   */
-  public boolean isSaturated(ChangeNumber changeNumber,
-    ServerHandler sourceHandler)
-  {
-    synchronized (msgQueue)
-    {
-      int size = msgQueue.count();
-
-      if ((maxReceiveQueue > 0) && (size >= maxReceiveQueue))
-        return true;
-
-      if ((sourceHandler.maxSendQueue > 0) &&
-        (size >= sourceHandler.maxSendQueue))
-        return true;
-
-      if (!msgQueue.isEmpty())
-      {
-        UpdateMsg firstUpdate = msgQueue.first();
-
-        if (firstUpdate != null)
-        {
-          long timeDiff = changeNumber.getTimeSec() -
-            firstUpdate.getChangeNumber().getTimeSec();
-
-          if ((maxReceiveDelay > 0) && (timeDiff >= maxReceiveDelay))
-            return true;
-
-          if ((sourceHandler.maxSendDelay > 0) &&
-            (timeDiff >= sourceHandler.maxSendDelay))
-            return true;
-        }
-      }
-      return false;
-    }
-  }
-
-  /**
-   * Check that the size of the Server Handler messages Queue has lowered
-   * below the limit and therefore allowing the reception of messages
-   * from other servers to restart.
-   * @param source The ServerHandler which was sending the update.
-   *        can be null.
-   * @return true if the processing can restart
-   */
-  public boolean restartAfterSaturation(ServerHandler source)
-  {
-    synchronized (msgQueue)
-    {
-      int queueSize = msgQueue.count();
-      if ((maxReceiveQueue > 0) && (queueSize >= restartReceiveQueue))
-        return false;
-      if ((source != null) && (source.maxSendQueue > 0) &&
-        (queueSize >= source.restartSendQueue))
-        return false;
-
-      if (!msgQueue.isEmpty())
-      {
-        UpdateMsg firstUpdate = msgQueue.first();
-        UpdateMsg lastUpdate = msgQueue.last();
-
-        if ((firstUpdate != null) && (lastUpdate != null))
-        {
-          long timeDiff = lastUpdate.getChangeNumber().getTimeSec() -
-            firstUpdate.getChangeNumber().getTimeSec();
-          if ((maxReceiveDelay > 0) && (timeDiff >= restartReceiveDelay))
-            return false;
-          if ((source != null) && (source.maxSendDelay > 0) && (timeDiff >=
-            source.restartSendDelay))
-            return false;
-        }
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Check if the server associated to this ServerHandler is a replication
-   * server.
-   * @return true if the server associated to this ServerHandler is a
-   *         replication server.
-   */
-  public boolean isReplicationServer()
-  {
-    return (!serverIsLDAPserver);
-  }
-
-  /**
-   * Get the number of message in the receive message queue.
-   * @return Size of the receive message queue.
-   */
-  public int getRcvMsgQueueSize()
-  {
-    synchronized (msgQueue)
-    {
-      /*
-       * When the server is up to date or close to be up to date,
-       * the number of updates to be sent is the size of the receive queue.
-       */
-      if (isFollowing())
-        return msgQueue.count();
-      else
-      {
-        /**
-         * When the server  is not able to follow, the msgQueue
-         * may become too large and therefore won't contain all the
-         * changes. Some changes may only be stored in the backing DB
-         * of the servers.
-         * The total size of the receive queue is calculated by doing
-         * the sum of the number of missing changes for every dbHandler.
-         */
-        ServerState dbState = replicationServerDomain.getDbServerState();
-        return ServerState.diffChanges(dbState, serverState);
-      }
-    }
+    session.publish(msg);
   }
 
   /**
@@ -1536,478 +458,133 @@
   }
 
   /**
-   * Get the older update time for that server.
-   * @return The older update time.
+   * Get the number of updates received from the server in assured safe data
+   * mode.
+   * @return The number of updates received from the server in assured safe data
+   * mode
    */
-  public long getOlderUpdateTime()
+  public int getAssuredSdReceivedUpdates()
   {
-    ChangeNumber olderUpdateCN = getOlderUpdateCN();
-    if (olderUpdateCN == null)
-      return 0;
-    return olderUpdateCN.getTime();
+    return assuredSdReceivedUpdates;
   }
 
   /**
-   * Get the older Change Number for that server.
-   * Returns null when the queue is empty.
-   * @return The older change number.
+   * Get the number of updates received from the server in assured safe data
+   * mode that timed out.
+   * @return The number of updates received from the server in assured safe data
+   * mode that timed out.
    */
-  public ChangeNumber getOlderUpdateCN()
+  public AtomicInteger getAssuredSdReceivedUpdatesTimeout()
   {
-    ChangeNumber result = null;
-    synchronized (msgQueue)
-    {
-      if (isFollowing())
-      {
-        if (msgQueue.isEmpty())
-        {
-          result = null;
-        } else
-        {
-          UpdateMsg msg = msgQueue.first();
-          result = msg.getChangeNumber();
-        }
-      } else
-      {
-        if (lateQueue.isEmpty())
-        {
-          // isFollowing is false AND lateQueue is empty
-          // We may be at the very moment when the writer has emptyed the
-          // lateQueue when it sent the last update. The writer will fill again
-          // the lateQueue when it will send the next update but we are not yet
-          // there. So let's take the last change not sent directly from
-          // the db.
-
-          ReplicationIteratorComparator comparator =
-            new ReplicationIteratorComparator();
-          SortedSet<ReplicationIterator> iteratorSortedSet =
-            new TreeSet<ReplicationIterator>(comparator);
-          try
-          {
-            // Build a list of candidates iterator (i.e. db i.e. server)
-            for (short serverId : replicationServerDomain.getServers())
-            {
-              // get the last already sent CN from that server
-              ChangeNumber lastCsn = serverState.getMaxChangeNumber(serverId);
-              // get an iterator in this server db from that last change
-              ReplicationIterator iterator =
-                replicationServerDomain.getChangelogIterator(serverId, lastCsn);
-              // if that iterator has changes, then it is a candidate
-              // it is added in the sorted list at a position given by its
-              // current change (see ReplicationIteratorComparator).
-              if ((iterator != null) && (iterator.getChange() != null))
-              {
-                iteratorSortedSet.add(iterator);
-              }
-            }
-            UpdateMsg msg = iteratorSortedSet.first().getChange();
-            result = msg.getChangeNumber();
-          } catch (Exception e)
-          {
-            result = null;
-          } finally
-          {
-            for (ReplicationIterator iterator : iteratorSortedSet)
-            {
-              iterator.releaseCursor();
-            }
-          }
-        } else
-        {
-          UpdateMsg msg = lateQueue.first();
-          result = msg.getChangeNumber();
-        }
-      }
-    }
-    return result;
+    return assuredSdReceivedUpdatesTimeout;
   }
 
   /**
-   * Check if the LDAP server can follow the speed of the other servers.
-   * @return true when the server has all the not yet sent changes
-   *         in its queue.
+   * Get the number of updates sent to the server in assured safe data mode.
+   * @return The number of updates sent to the server in assured safe data mode
    */
-  public boolean isFollowing()
+  public int getAssuredSdSentUpdates()
   {
-    return following;
+    return assuredSdSentUpdates;
   }
 
   /**
-   * Set the following flag of this server.
-   * @param following the value that should be set.
+   * Get the number of updates sent to the server in assured safe data mode that
+   * timed out.
+   * @return The number of updates sent to the server in assured safe data mode
+   * that timed out.
    */
-  public void setFollowing(boolean following)
+  public AtomicInteger getAssuredSdSentUpdatesTimeout()
   {
-    this.following = following;
+    return assuredSdSentUpdatesTimeout;
   }
 
   /**
-   * Add an update to the list of updates that must be sent to the server
-   * managed by this ServerHandler.
+   * Get the number of updates received from the server in assured safe read
+   * mode.
+   * @return The number of updates received from the server in assured safe read
+   * mode
+   */
+  public int getAssuredSrReceivedUpdates()
+  {
+    return assuredSrReceivedUpdates;
+  }
+
+  /**
+   * Get the number of updates received from the server in assured safe read
+   * mode that timed out.
+   * @return The number of updates received from the server in assured safe read
+   * mode that timed out.
+   */
+  public AtomicInteger getAssuredSrReceivedUpdatesTimeout()
+  {
+    return assuredSrReceivedUpdatesTimeout;
+  }
+
+  /**
+   * Get the number of updates sent to the server in assured safe read mode.
+   * @return The number of updates sent to the server in assured safe read mode
+   */
+  public int getAssuredSrSentUpdates()
+  {
+    return assuredSrSentUpdates;
+  }
+
+  /**
+   * Get the number of updates sent to the server in assured safe read mode that
+   * timed out.
+   * @return The number of updates sent to the server in assured safe read mode
+   * that timed out.
+   */
+  public AtomicInteger getAssuredSrSentUpdatesTimeout()
+  {
+    return assuredSrSentUpdatesTimeout;
+  }
+
+  /**
+   * Returns the Replication Server Domain to which belongs this server handler.
    *
-   * @param update The update that must be added to the list of updates.
-   * @param sourceHandler The server that sent the update.
+   * @return The replication server domain.
    */
-  public void add(UpdateMsg update, ServerHandler sourceHandler)
+  public ReplicationServerDomain getDomain()
   {
-    synchronized (msgQueue)
-    {
-      /*
-       * If queue was empty the writer thread was probably asleep
-       * waiting for some changes, wake it up
-       */
-      if (msgQueue.isEmpty())
-        msgQueue.notify();
-
-      msgQueue.add(update);
-
-      /* TODO : size should be configurable
-       * and larger than max-receive-queue-size
-       */
-      while ((msgQueue.count() > maxQueueSize) ||
-          (msgQueue.bytesCount() > maxQueueBytesSize))
-      {
-        setFollowing(false);
-        msgQueue.removeFirst();
-      }
-    }
-
-    if (isSaturated(update.getChangeNumber(), sourceHandler))
-    {
-      sourceHandler.setSaturated(true);
-    }
-
-  }
-
-  private void setSaturated(boolean value)
-  {
-    flowControl = value;
+    return this.replicationServerDomain;
   }
 
   /**
-   * Select the next update that must be sent to the server managed by this
-   * ServerHandler.
-   *
-   * @return the next update that must be sent to the server managed by this
-   *         ServerHandler.
+   * Returns the value of generationId for that handler.
+   * @return The value of the generationId.
    */
-  public UpdateMsg take()
+  public long getGenerationId()
   {
-    boolean interrupted = true;
-    UpdateMsg msg = getnextMessage();
-
-    /*
-     * 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.
-     */
-    if (++saturationCount > 10)
-    {
-      saturationCount = 0;
-      try
-      {
-        replicationServerDomain.checkAllSaturation();
-      } catch (IOException e)
-      {
-      }
-    }
-    boolean acquired = false;
-    do
-    {
-      try
-      {
-        acquired = sendWindow.tryAcquire((long) 500, TimeUnit.MILLISECONDS);
-        interrupted = false;
-      } catch (InterruptedException e)
-      {
-        // loop until not interrupted
-      }
-    } while (((interrupted) || (!acquired)) && (!shutdownWriter));
-    if (msg != null)
-    {
-      incrementOutCount();
-      if (msg.isAssured())
-      {
-        if (msg.getAssuredMode() == AssuredMode.SAFE_READ_MODE)
-        {
-          incrementAssuredSrSentUpdates();
-        } else
-        {
-          if (!isLDAPserver())
-            incrementAssuredSdSentUpdates();
-        }
-      }
-    }
-    return msg;
+    return generationId;
   }
 
   /**
-   * Get the next update that must be sent to the server
-   * from the message queue or from the database.
-   *
-   * @return The next update that must be sent to the server.
+   * Gets the group id of the server represented by this object.
+   * @return The group id of the server represented by this object.
    */
-  private UpdateMsg getnextMessage()
+  public byte getGroupId()
   {
-    UpdateMsg msg;
-    while (activeWriter == true)
-    {
-      if (following == false)
-      {
-        /* this server is late with regard to some other masters
-         * in the topology or just joined the topology.
-         * In such cases, we can't keep all changes in the queue
-         * without saturating the memory, we therefore use
-         * a lateQueue that is filled with a few changes from the changelogDB
-         * If this server is able to close the gap, it will start using again
-         * the regular msgQueue later.
-         */
-        if (lateQueue.isEmpty())
-        {
-          /*
-           * Start from the server State
-           * Loop until the queue high mark or until no more changes
-           *   for each known LDAP master
-           *      get the next CSN after this last one :
-           *         - try to get next from the file
-           *         - if not found in the file
-           *             - try to get the next from the queue
-           *   select the smallest of changes
-           *   check if it is in the memory tree
-           *     yes : lock memory tree.
-           *           check all changes from the list, remove the ones that
-           *           are already sent
-           *           unlock memory tree
-           *           restart as usual
-           *   load this change on the delayList
-           *
-           */
-          ReplicationIteratorComparator comparator =
-            new ReplicationIteratorComparator();
-          SortedSet<ReplicationIterator> iteratorSortedSet =
-            new TreeSet<ReplicationIterator>(comparator);
-          /* fill the lateQueue */
-          for (short serverId : replicationServerDomain.getServers())
-          {
-            ChangeNumber lastCsn = serverState.getMaxChangeNumber(serverId);
-            ReplicationIterator iterator =
-              replicationServerDomain.getChangelogIterator(serverId, lastCsn);
-            if (iterator != null)
-            {
-              if (iterator.getChange() != null)
-              {
-                iteratorSortedSet.add(iterator);
-              } else
-              {
-                iterator.releaseCursor();
-              }
-            }
-          }
-
-          // The loop below relies on the fact that it is sorted based
-          // on the currentChange of each iterator to consider the next
-          // change across all servers.
-          // Hence it is necessary to remove and eventual add again an iterator
-          // when looping in order to keep consistent the order of the
-          // iterators (see ReplicationIteratorComparator.
-          while (!iteratorSortedSet.isEmpty() &&
-                 (lateQueue.count()<100) &&
-                 (lateQueue.bytesCount()<50000) )
-          {
-            ReplicationIterator iterator = iteratorSortedSet.first();
-            iteratorSortedSet.remove(iterator);
-            lateQueue.add(iterator.getChange());
-            if (iterator.next())
-              iteratorSortedSet.add(iterator);
-            else
-              iterator.releaseCursor();
-          }
-          for (ReplicationIterator iterator : iteratorSortedSet)
-          {
-            iterator.releaseCursor();
-          }
-          /*
-           * Check if the first change in the lateQueue is also on the regular
-           * queue
-           */
-          if (lateQueue.isEmpty())
-          {
-            synchronized (msgQueue)
-            {
-              if ((msgQueue.count() < maxQueueSize) &&
-                  (msgQueue.bytesCount() < maxQueueBytesSize))
-              {
-                setFollowing(true);
-              }
-            }
-          } else
-          {
-            msg = lateQueue.first();
-            synchronized (msgQueue)
-            {
-              if (msgQueue.contains(msg))
-              {
-                /* we finally catch up with the regular queue */
-                setFollowing(true);
-                lateQueue.clear();
-                UpdateMsg msg1;
-                do
-                {
-                  msg1 = msgQueue.removeFirst();
-                } while (!msg.getChangeNumber().equals(msg1.getChangeNumber()));
-                this.updateServerState(msg);
-                return msg;
-              }
-            }
-          }
-        } else
-        {
-          /* get the next change from the lateQueue */
-          msg = lateQueue.removeFirst();
-          this.updateServerState(msg);
-          return msg;
-        }
-      }
-      synchronized (msgQueue)
-      {
-        if (following == true)
-        {
-          try
-          {
-            while (msgQueue.isEmpty() && (following == true))
-            {
-              msgQueue.wait(500);
-              if (!activeWriter)
-                return null;
-            }
-          } catch (InterruptedException e)
-          {
-            return null;
-          }
-          if (following == true)
-          {
-            msg = msgQueue.removeFirst();
-            if (this.updateServerState(msg))
-            {
-              /*
-               * Only push the message if it has not yet been seen
-               * by the other server.
-               * Otherwise just loop to select the next message.
-               */
-              return msg;
-            }
-          }
-        }
-      }
-    /*
-     * Need to loop because following flag may have gone to false between
-     * the first check at the beginning of this method
-     * and the second check just above.
-     */
-    }
-    return null;
+    return groupId;
   }
 
   /**
-   * Update the serverState with the last message sent.
-   *
-   * @param msg the last update sent.
-   * @return boolean indicating if the update was meaningful.
+   * Get our heartbeat interval.
+   * @return Our heartbeat interval.
    */
-  public boolean updateServerState(UpdateMsg msg)
+  public long getHeartbeatInterval()
   {
-    return serverState.update(msg.getChangeNumber());
+    return heartbeatInterval;
   }
 
   /**
-   * Get the state of this server.
-   *
-   * @return ServerState the state for this server..
+   * Get the count of updates received from the server.
+   * @return the count of update received from the server.
    */
-  public ServerState getServerState()
+  public int getInCount()
   {
-    return serverState;
-  }
-
-  /**
-   * Sends an ack message to the server represented by this object.
-   *
-   * @param ack The ack message to be sent.
-   * @throws IOException In case of Exception thrown sending the ack.
-   */
-  public void sendAck(AckMsg ack) throws IOException
-  {
-    session.publish(ack);
-  }
-
-  /**
-   * Check type of server handled.
-   *
-   * @return true if the handled server is an LDAP server.
-   *         false if the handled server is a replicationServer
-   */
-  public boolean isLDAPserver()
-  {
-    return serverIsLDAPserver;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void initializeMonitorProvider(MonitorProviderCfg configuration)
-    throws ConfigException, InitializationException
-  {
-    // Nothing to do for now
-  }
-
-  /**
-   * Retrieves the name of this monitor provider.  It should be unique among all
-   * monitor providers, including all instances of the same monitor provider.
-   *
-   * @return  The name of this monitor provider.
-   */
-  @Override
-  public String getMonitorInstanceName()
-  {
-    String str = serverURL + " " + String.valueOf(serverId);
-
-    if (serverIsLDAPserver)
-      return "Connected Replica " + str +
-                ",cn=" + replicationServerDomain.getMonitorInstanceName();
-    else
-      return "Connected Replication Server " + str +
-                ",cn=" + replicationServerDomain.getMonitorInstanceName();
-  }
-
-  /**
-   * Retrieves the length of time in milliseconds that should elapse between
-   * calls to the <CODE>updateMonitorData()</CODE> method.  A negative or zero
-   * return value indicates that the <CODE>updateMonitorData()</CODE> method
-   * should not be periodically invoked.
-   *
-   * @return  The length of time in milliseconds that should elapse between
-   *          calls to the <CODE>updateMonitorData()</CODE> method.
-   */
-  @Override
-  public long getUpdateInterval()
-  {
-    /* we don't wont to do polling on this monitor */
-    return 0;
-  }
-
-  /**
-   * Performs any processing periodic processing that may be desired to update
-   * the information associated with this monitor.  Note that best-effort
-   * attempts will be made to ensure that calls to this method come
-   * <CODE>getUpdateInterval()</CODE> milliseconds apart, but no guarantees will
-   * be made.
-   */
-  @Override
-  public void updateMonitorData()
-  {
-    // As long as getUpdateInterval() returns 0, this will never get called
+    return inCount;
   }
 
   /**
@@ -2021,101 +598,11 @@
   @Override
   public ArrayList<Attribute> getMonitorData()
   {
-    ArrayList<Attribute> attributes = new ArrayList<Attribute>();
-    if (serverIsLDAPserver)
-    {
-      attributes.add(Attributes.create("replica", serverURL));
-      attributes.add(Attributes.create("connected-to",
-          this.replicationServerDomain.getReplicationServer()
-              .getMonitorInstanceName()));
+    // Get the generic ones
+    ArrayList<Attribute> attributes = super.getMonitorData();
 
-    }
-    else
-    {
-      attributes.add(Attributes.create("Replication-Server",
-          serverURL));
-    }
-    attributes.add(Attributes.create("server-id", String
-        .valueOf(serverId)));
-    attributes.add(Attributes.create("domain-name", baseDn.toString()));
-
-    try
-    {
-      MonitorData md;
-      md = replicationServerDomain.computeMonitorData();
-
-      if (serverIsLDAPserver)
-      {
-        // Oldest missing update
-        Long approxFirstMissingDate = md.getApproxFirstMissingDate(serverId);
-        if ((approxFirstMissingDate != null) && (approxFirstMissingDate > 0))
-        {
-          Date date = new Date(approxFirstMissingDate);
-          attributes.add(Attributes.create(
-              "approx-older-change-not-synchronized", date.toString()));
-          attributes.add(Attributes.create(
-              "approx-older-change-not-synchronized-millis", String
-              .valueOf(approxFirstMissingDate)));
-        }
-
-        // Missing changes
-        long missingChanges = md.getMissingChanges(serverId);
-        attributes.add(Attributes.create("missing-changes", String
-            .valueOf(missingChanges)));
-
-        // Replication delay
-        long delay = md.getApproxDelay(serverId);
-        attributes.add(Attributes.create("approximate-delay", String
-            .valueOf(delay)));
-
-        /* get the Server State */
-        AttributeBuilder builder = new AttributeBuilder("server-state");
-        ServerState state = md.getLDAPServerState(serverId);
-        if (state != null)
-        {
-          for (String str : state.toStringSet())
-          {
-            builder.add(str);
-          }
-          attributes.add(builder.toAttribute());
-        }
-      }
-      else
-      {
-        // Missing changes
-        long missingChanges = md.getMissingChangesRS(serverId);
-        attributes.add(Attributes.create("missing-changes", String
-            .valueOf(missingChanges)));
-
-        /* get the Server State */
-        AttributeBuilder builder = new AttributeBuilder("server-state");
-        ServerState state = md.getRSStates(serverId);
-        if (state != null)
-        {
-          for (String str : state.toStringSet())
-          {
-            builder.add(str);
-          }
-          attributes.add(builder.toAttribute());
-        }
-      }
-    }
-    catch (Exception e)
-    {
-      Message message =
-        ERR_ERROR_RETRIEVING_MONITOR_DATA.get(stackTraceToSingleLineString(e));
-      // We failed retrieving the monitor data.
-      attributes.add(Attributes.create("error", message.toString()));
-    }
-
-    attributes.add(
-        Attributes.create("queue-size", String.valueOf(msgQueue.count())));
-    attributes.add(
-        Attributes.create(
-            "queue-size-bytes", String.valueOf(msgQueue.bytesCount())));
-    attributes.add(
-        Attributes.create(
-            "following", String.valueOf(following)));
+    attributes.add(Attributes.create("server-id", String.valueOf(serverId)));
+    attributes.add(Attributes.create("domain-name", getServiceId().toString()));
 
     // Deprecated
     attributes.add(Attributes.create("max-waiting-changes", String
@@ -2129,23 +616,23 @@
     attributes.add(Attributes.create("assured-sr-received-updates", String
         .valueOf(getAssuredSrReceivedUpdates())));
     attributes.add(Attributes.create("assured-sr-received-updates-timeout",
-      String .valueOf(getAssuredSrReceivedUpdatesTimeout())));
+        String .valueOf(getAssuredSrReceivedUpdatesTimeout())));
     attributes.add(Attributes.create("assured-sr-sent-updates", String
         .valueOf(getAssuredSrSentUpdates())));
     attributes.add(Attributes.create("assured-sr-sent-updates-timeout", String
         .valueOf(getAssuredSrSentUpdatesTimeout())));
     attributes.add(Attributes.create("assured-sd-received-updates", String
         .valueOf(getAssuredSdReceivedUpdates())));
-    if (!isLDAPserver())
+    if (!isDataServer())
     {
       attributes.add(Attributes.create("assured-sd-sent-updates",
-        String.valueOf(getAssuredSdSentUpdates())));
+          String.valueOf(getAssuredSdSentUpdates())));
       attributes.add(Attributes.create("assured-sd-sent-updates-timeout",
-        String.valueOf(getAssuredSdSentUpdatesTimeout())));
+          String.valueOf(getAssuredSdSentUpdatesTimeout())));
     } else
     {
       attributes.add(Attributes.create("assured-sd-received-updates-timeout",
-        String.valueOf(getAssuredSdReceivedUpdatesTimeout())));
+          String.valueOf(getAssuredSdReceivedUpdatesTimeout())));
     }
 
     // Window stats
@@ -2170,44 +657,484 @@
   }
 
   /**
+   * Retrieves the name of this monitor provider.  It should be unique among all
+   * monitor providers, including all instances of the same monitor provider.
+   *
+   * @return  The name of this monitor provider.
+   */
+  @Override
+  public abstract String getMonitorInstanceName();
+
+  /**
+   * Get the older update time for that server.
+   * @return The older update time.
+   */
+  public long getOlderUpdateTime()
+  {
+    ChangeNumber olderUpdateCN = getOlderUpdateCN();
+    if (olderUpdateCN == null)
+      return 0;
+    return olderUpdateCN.getTime();
+  }
+
+  /**
+   * Get the count of updates sent to this server.
+   * @return  The count of update sent to this server.
+   */
+  public int getOutCount()
+  {
+    return outCount;
+  }
+
+  /**
+   * Gets the protocol version used with this remote server.
+   * @return The protocol version used with this remote server.
+   */
+  public short getProtocolVersion()
+  {
+    return protocolVersion;
+  }
+
+  /**
+   * get the Server Id.
+   *
+   * @return the ID of the server to which this object is linked
+   */
+  public short getServerId()
+  {
+    return serverId;
+  }
+
+  /**
+   * Retrieves the URL for this server handler.
+   *
+   * @return  The URL for this server handler, in the form of an address and
+   *          port separated by a colon.
+   */
+  public String getServerURL()
+  {
+    return serverURL;
+  }
+
+  /**
+   * Return the ServerStatus.
+   * @return The server status.
+   */
+  protected abstract ServerStatus getStatus();
+
+  /**
+   * Retrieves the length of time in milliseconds that should elapse between
+   * calls to the <CODE>updateMonitorData()</CODE> method.  A negative or zero
+   * return value indicates that the <CODE>updateMonitorData()</CODE> method
+   * should not be periodically invoked.
+   *
+   * @return  The length of time in milliseconds that should elapse between
+   *          calls to the <CODE>updateMonitorData()</CODE> method.
+   */
+  @Override
+  public long getUpdateInterval()
+  {
+    /* we don't wont to do polling on this monitor */
+    return 0;
+  }
+
+  /**
+   * Increment the number of updates received from the server in assured safe
+   * data mode.
+   */
+  public void incrementAssuredSdReceivedUpdates()
+  {
+    assuredSdReceivedUpdates++;
+  }
+
+  /**
+   * Increment the number of updates received from the server in assured safe
+   * data mode that timed out.
+   */
+  public void incrementAssuredSdReceivedUpdatesTimeout()
+  {
+    assuredSdReceivedUpdatesTimeout.incrementAndGet();
+  }
+
+  /**
+   * Increment the number of updates sent to the server in assured safe data
+   * mode.
+   */
+  public void incrementAssuredSdSentUpdates()
+  {
+    assuredSdSentUpdates++;
+  }
+
+  /**
+   * Increment the number of updates sent to the server in assured safe data
+   * mode that timed out.
+   */
+  public void incrementAssuredSdSentUpdatesTimeout()
+  {
+    assuredSdSentUpdatesTimeout.incrementAndGet();
+  }
+
+  /**
+   * Increment the number of updates received from the server in assured safe
+   * read mode.
+   */
+  public void incrementAssuredSrReceivedUpdates()
+  {
+    assuredSrReceivedUpdates++;
+  }
+
+  /**
+   * Increment the number of updates received from the server in assured safe
+   * read mode that timed out.
+   */
+  public void incrementAssuredSrReceivedUpdatesTimeout()
+  {
+    assuredSrReceivedUpdatesTimeout.incrementAndGet();
+  }
+
+  /**
+   * Increment the number of updates sent to the server in assured safe read
+   * mode.
+   */
+  public void incrementAssuredSrSentUpdates()
+  {
+    assuredSrSentUpdates++;
+  }
+
+  /**
+   * Increment the number of updates sent to the server in assured safe read
+   * mode that timed out.
+   */
+  public void incrementAssuredSrSentUpdatesTimeout()
+  {
+    assuredSrSentUpdatesTimeout.incrementAndGet();
+  }
+
+  /**
+   * Increase the counter of update received from the server.
+   */
+  public void incrementInCount()
+  {
+    inCount++;
+  }
+
+  /**
+   * Increase the counter of updates sent to the server.
+   */
+  public void incrementOutCount()
+  {
+    outCount++;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void initializeMonitorProvider(MonitorProviderCfg configuration)
+  throws ConfigException, InitializationException
+  {
+    // Nothing to do for now
+  }
+
+  /**
+   * Check if the server associated to this ServerHandler is a data server
+   * in the topology.
+   * @return true if the server is a data server.
+   */
+  public abstract boolean isDataServer();
+
+  /**
+   * Check if the server associated to this ServerHandler is a replication
+   * server.
+   * @return true if the server is a replication server.
+   */
+  public boolean isReplicationServer()
+  {
+    return (!this.isDataServer());
+  }
+
+  /**
+   * Lock the domain potentially with a timeout.
+   * @param timedout The provided timeout.
+   * @throws DirectoryException When an exception occurs.
+   */
+  protected void lockDomain(boolean timedout)
+  throws DirectoryException
+  {
+    // The handshake phase must be done by blocking any access to structures
+    // keeping info on connected servers, so that one can safely check for
+    // pre-existence of a server, send a coherent snapshot of known topology
+    // to peers, update the local view of the topology...
+    //
+    // For instance a kind of problem could be that while we connect with a
+    // peer RS, a DS is connecting at the same time and we could publish the
+    // connected DSs to the peer RS forgetting this last DS in the TopologyMsg.
+    //
+    // This method and every others that need to read/make changes to the
+    // structures holding topology for the domain should:
+    // - call ReplicationServerDomain.lock()
+    // - read/modify structures
+    // - call ReplicationServerDomain.release()
+    //
+    // More information is provided in comment of ReplicationServerDomain.lock()
+
+    // If domain already exists, lock it until handshake is finished otherwise
+    // it will be created and locked later in the method
+    try
+    {
+      if (!timedout)
+      {
+        // !timedout
+        if (!replicationServerDomain.hasLock())
+          replicationServerDomain.lock();
+      }
+      else
+      {
+        // timedout
+        /**
+         * Take the lock on the domain.
+         * WARNING: Here we try to acquire the lock with a timeout. This
+         * is for preventing a deadlock that may happen if there are cross
+         * connection attempts (for same domain) from this replication
+         * server and from a peer one:
+         * Here is the scenario:
+         * - RS1 connect thread takes the domain lock and starts
+         * connection to RS2
+         * - at the same time RS2 connect thread takes his domain lock and
+         * start connection to RS2
+         * - RS2 listen thread starts processing received
+         * ReplServerStartMsg from RS1 and wants to acquire the lock on
+         * the domain (here) but cannot as RS2 connect thread already has
+         * it
+         * - RS1 listen thread starts processing received
+         * ReplServerStartMsg from RS2 and wants to acquire the lock on
+         * the domain (here) but cannot as RS1 connect thread already has
+         * it
+         * => Deadlock: 4 threads are locked.
+         * So to prevent that in such situation, the listen threads here
+         * will both timeout trying to acquire the lock. The random time
+         * for the timeout should allow on connection attempt to be
+         * aborted whereas the other one should have time to finish in the
+         * same time.
+         * Warning: the minimum time (3s) should be big enough to allow
+         * normal situation connections to terminate. The added random
+         * time should represent a big enough range so that the chance to
+         * have one listen thread timing out a lot before the peer one is
+         * great. When the first listen thread times out, the remote
+         * connect thread should release the lock and allow the peer
+         * listen thread to take the lock it was waiting for and process
+         * the connection attempt.
+         */
+        Random random = new Random();
+        int randomTime = random.nextInt(6); // Random from 0 to 5
+        // Wait at least 3 seconds + (0 to 5 seconds)
+        long timeout = (long) (3000 + ( randomTime * 1000 ) );
+        boolean noTimeout = replicationServerDomain.tryLock(timeout);
+        if (!noTimeout)
+        {
+          // Timeout
+          Message message = NOTE_TIMEOUT_WHEN_CROSS_CONNECTION.get(
+              getServiceId(),
+              Short.toString(serverId),
+              Short.toString(replicationServerId));
+          throw new DirectoryException(ResultCode.OTHER, message);
+        }
+      }
+    }
+    catch (InterruptedException e)
+    {
+      // Thread interrupted
+      Message message = ERR_EXCEPTION_LOCKING_RS_DOMAIN.get(e.getMessage());
+      logError(message);
+    }
+  }
+
+  /**
+   * Processes a routable message.
+   *
+   * @param msg The message to be processed.
+   */
+  public void process(RoutableMsg msg)
+  {
+    if (debugEnabled())
+      TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
+          getMonitorInstanceName() + this +
+          " processes received msg:\n" + msg);
+    replicationServerDomain.process(msg, this);
+  }
+
+  /**
+   * Process the reception of a WindowProbeMsg message.
+   *
+   * @param  windowProbeMsg The message to process.
+   *
+   * @throws IOException    When the session becomes unavailable.
+   */
+  public void process(WindowProbeMsg windowProbeMsg) throws IOException
+  {
+    if (rcvWindow > 0)
+    {
+      // The LDAP server believes that its window is closed
+      // while it is not, this means that some problem happened in the
+      // window exchange procedure !
+      // lets update the LDAP server with out current window size and hope
+      // that everything will work better in the futur.
+      // TODO also log an error message.
+      WindowMsg msg = new WindowMsg(rcvWindow);
+      session.publish(msg);
+    } else
+    {
+      // Both the LDAP server and the replication server believes that the
+      // window is closed. Lets check the flowcontrol in case we
+      // can now resume operations and send a windowMessage if necessary.
+      checkWindow();
+    }
+  }
+
+  /**
+   * Send an InitializeRequestMessage to the server connected through this
+   * handler.
+   *
+   * @param msg The message to be processed
+   * @throws IOException when raised by the underlying session
+   */
+  public void send(RoutableMsg msg) throws IOException
+  {
+    if (debugEnabled())
+      TRACER.debugInfo("In " +
+          replicationServerDomain.getReplicationServer().
+          getMonitorInstanceName() + this +
+          " publishes message:\n" + msg);
+    session.publish(msg);
+  }
+
+  /**
+   * Sends an ack message to the server represented by this object.
+   *
+   * @param ack The ack message to be sent.
+   * @throws IOException In case of Exception thrown sending the ack.
+   */
+  public void sendAck(AckMsg ack) throws IOException
+  {
+    session.publish(ack);
+  }
+
+  /**
+   * Send an ErrorMsg to the peer.
+   *
+   * @param errorMsg The message to be sent
+   * @throws IOException when raised by the underlying session
+   */
+  public void sendError(ErrorMsg errorMsg) throws IOException
+  {
+    session.publish(errorMsg);
+  }
+
+  /**
+   * Send the ReplServerStartMsg to the remote server (RS or DS).
+   * @param requestedProtocolVersion The provided protocol version.
+   * @return The ReplServerStartMsg sent.
+   * @throws IOException When an exception occurs.
+   */
+  public ReplServerStartMsg sendStartToRemote(short requestedProtocolVersion)
+  throws IOException
+  {
+    this.localGenerationId = replicationServerDomain.getGenerationId();
+    ReplServerStartMsg outReplServerStartMsg
+    = new ReplServerStartMsg(
+        replicationServerId,
+        replicationServerURL,
+        getServiceId(),
+        maxRcvWindow,
+        replicationServerDomain.getDbServerState(),
+        protocolVersion,
+        localGenerationId,
+        sslEncryption,
+        getLocalGroupId(),
+        replicationServerDomain.
+        getReplicationServer().getDegradedStatusThreshold());
+
+    if (requestedProtocolVersion>0)
+      session.publish(outReplServerStartMsg, requestedProtocolVersion);
+    else
+      session.publish(outReplServerStartMsg);
+
+    return outReplServerStartMsg;
+  }
+
+  /**
+   * Sends the provided TopologyMsg to the peer server.
+   *
+   * @param topoMsg The TopologyMsg message to be sent.
+   * @throws IOException When it occurs while sending the message,
+   *
+   */
+  public void sendTopoInfo(TopologyMsg topoMsg)
+  throws IOException
+  {
+    // V1 Rs do not support the TopologyMsg
+    if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
+    {
+      session.publish(topoMsg);
+    }
+  }
+
+  /**
+   * Set a new generation ID.
+   *
+   * @param generationId The new generation ID
+   *
+   */
+  public void setGenerationId(long generationId)
+  {
+    this.generationId = generationId;
+  }
+
+  /**
+   * Sets the replication server domain associated.
+   * @param rsd The provided replication server domain.
+   */
+  protected void setReplicationServerDomain(ReplicationServerDomain rsd)
+  {
+    this.replicationServerDomain = rsd;
+  }
+
+  /**
+   * Sets the window size when used when sending to the remote.
+   * @param size The provided window size.
+   */
+  protected void setSendWindowSize(int size)
+  {
+    this.sendWindowSize = size;
+  }
+
+  /**
+   * Requests to shutdown the writer.
+   */
+  protected void shutdownWriter()
+  {
+    shutdownWriter = true;
+  }
+
+  /**
    * Shutdown This ServerHandler.
    */
   public void shutdown()
   {
-    /*
-     * Shutdown ServerWriter
-     */
-    shutdownWriter = true;
-    activeWriter = false;
-    synchronized (msgQueue)
-    {
-      /* wake up the writer thread on an empty queue so that it disappear */
-      msgQueue.clear();
-      msgQueue.notify();
-      msgQueue.notifyAll();
-    }
+    shutdownWriter();
+    setConsumerActive(false);
+    super.shutdown();
 
-    /*
-     * Close session to end ServerReader or ServerWriter
-     */
-    try
+    if (session != null)
     {
-      session.close();
-    } catch (IOException e)
-    {
-      // ignore.
-    }
-
-    /*
-     * Stop the remote LSHandler
-     */
-    synchronized (directoryServers)
-    {
-      for (LightweightServerHandler lsh : directoryServers.values())
+      // Close session to end ServerReader or ServerWriter
+      try
       {
-        lsh.stopHandler();
+        session.close();
+      } catch (IOException e)
+      {
+        // ignore.
       }
-      directoryServers.clear();
     }
 
     /*
@@ -2240,68 +1167,93 @@
     {
       // don't try anymore to join and return.
     }
+    if (debugEnabled())
+      TRACER.debugInfo("SH.shutdowned(" + this + ")");
   }
 
   /**
-   * {@inheritDoc}
-   */
-  @Override
-  public String toString()
-  {
-    String localString;
-    if (serverId != 0)
-    {
-      if (serverIsLDAPserver)
-        localString = "Directory Server ";
-      else
-        localString = "Replication Server ";
-
-
-      localString += serverId + " " + serverURL + " " + baseDn;
-    } else
-      localString = "Unknown server";
-
-    return localString;
-  }
-
-  /**
-   * Decrement the protocol window, then check if it is necessary
-   * to send a WindowMsg and send it.
+   * Select the next update that must be sent to the server managed by this
+   * ServerHandler.
    *
-   * @throws IOException when the session becomes unavailable.
+   * @return the next update that must be sent to the server managed by this
+   *         ServerHandler.
    */
-  public synchronized void decAndCheckWindow() throws IOException
+  public UpdateMsg take()
   {
-    rcvWindow--;
-    checkWindow();
-  }
+    boolean interrupted = true;
+    UpdateMsg msg = getnextMessage(true); // synchronous:block until msg
 
-  /**
-   * Check the protocol window and send WindowMsg if necessary.
-   *
-   * @throws IOException when the session becomes unavailable.
-   */
-  public synchronized void checkWindow() throws IOException
-  {
-    if (rcvWindow < rcvWindowSizeHalf)
+    /*
+     * 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.
+     */
+    if (++saturationCount > 10)
     {
-      if (flowControl)
+      saturationCount = 0;
+      try
       {
-        if (replicationServerDomain.restartAfterSaturation(this))
-        {
-          flowControl = false;
-        }
-      }
-      if (!flowControl)
+        replicationServerDomain.checkAllSaturation();
+      } catch (IOException e)
       {
-        WindowMsg msg = new WindowMsg(rcvWindowSizeHalf);
-        session.publish(msg);
-        rcvWindow += rcvWindowSizeHalf;
       }
     }
+    boolean acquired = false;
+    do
+    {
+      try
+      {
+        acquired = sendWindow.tryAcquire((long) 500, TimeUnit.MILLISECONDS);
+        interrupted = false;
+      } catch (InterruptedException e)
+      {
+        // loop until not interrupted
+      }
+    } while (((interrupted) || (!acquired)) && (!shutdownWriter));
+    if (msg != null)
+    {
+      incrementOutCount();
+
+      if (msg.isAssured())
+      {
+        if (msg.getAssuredMode() == AssuredMode.SAFE_READ_MODE)
+        {
+          incrementAssuredSrSentUpdates();
+        } else
+        {
+          if (!isDataServer())
+            incrementAssuredSdSentUpdates();
+        }
+      }
+    }
+    return msg;
   }
 
   /**
+   * Creates a RSInfo structure representing this remote RS.
+   * @return The RSInfo structure representing this remote RS
+   */
+  public RSInfo toRSInfo()
+  {
+    RSInfo rsInfo = new RSInfo(serverId, generationId, groupId);
+
+    return rsInfo;
+  }
+
+  /**
+   * Performs any processing periodic processing that may be desired to update
+   * the information associated with this monitor.  Note that best-effort
+   * attempts will be made to ensure that calls to this method come
+   * <CODE>getUpdateInterval()</CODE> milliseconds apart, but no guarantees will
+   * be made.
+   */
+  @Override
+  public void updateMonitorData()
+  {
+    // As long as getUpdateInterval() returns 0, this will never get called
+  }
+  /**
    * Update the send window size based on the credit specified in the
    * given window message.
    *
@@ -2314,510 +1266,122 @@
   }
 
   /**
-   * Get our heartbeat interval.
-   * @return Our heartbeat interval.
+   * Log the messages involved in the start handshake.
+   * @param inStartMsg The message received first.
+   * @param outStartMsg The message sent in response.
    */
-  public long getHeartbeatInterval()
-  {
-    return heartbeatInterval;
-  }
-
-  /**
-   * Processes a routable message.
-   *
-   * @param msg The message to be processed.
-   */
-  public void process(RoutableMsg msg)
+  protected void logStartHandshakeRCVandSND(
+      StartMsg inStartMsg,
+      StartMsg outStartMsg)
   {
     if (debugEnabled())
-      TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
-        getMonitorInstanceName() +
-        " SH for remote server " + this.getMonitorInstanceName() + ":" +
-        "\nprocesses received msg:\n" + msg);
-    replicationServerDomain.process(msg, this);
-  }
-
-  /**
-   * Sends the provided TopologyMsg to the peer server.
-   *
-   * @param topoMsg The TopologyMsg message to be sent.
-   * @throws IOException When it occurs while sending the message,
-   *
-   */
-  public void sendTopoInfo(TopologyMsg topoMsg)
-    throws IOException
-  {
-    // V1 Rs do not support the TopologyMsg
-    if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
     {
-      if (debugEnabled())
-        TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
-          getMonitorInstanceName() +
-          " SH for remote server " + this.getMonitorInstanceName() + ":" +
-          "\nsends message:\n" + topoMsg);
-
-      session.publish(topoMsg);
-    }
-  }
-
-  /**
-   * Stores topology information received from a peer RS and that must be kept
-   * in RS handler.
-   *
-   * @param topoMsg The received topology message
-   */
-  public void receiveTopoInfoFromRS(TopologyMsg topoMsg)
-  {
-    // Store info for remote RS
-    List<RSInfo> rsInfos = topoMsg.getRsList();
-    // List should only contain RS info for sender
-    RSInfo rsInfo = rsInfos.get(0);
-    generationId = rsInfo.getGenerationId();
-    groupId = rsInfo.getGroupId();
-
-    /**
-     * Store info for DSs connected to the peer RS
-     */
-    List<DSInfo> dsInfos = topoMsg.getDsList();
-
-    synchronized (directoryServers)
-    {
-      // Removes the existing structures
-      for (LightweightServerHandler lsh : directoryServers.values())
-      {
-        lsh.stopHandler();
-      }
-      directoryServers.clear();
-
-      // Creates the new structure according to the message received.
-      for (DSInfo dsInfo : dsInfos)
-      {
-        LightweightServerHandler lsh = new LightweightServerHandler(this,
-            serverId, dsInfo.getDsId(), dsInfo.getGenerationId(),
-            dsInfo.getGroupId(), dsInfo.getStatus(), dsInfo.getRefUrls(),
-            dsInfo.isAssured(), dsInfo.getAssuredMode(),
-            dsInfo.getSafeDataLevel());
-        lsh.startHandler();
-        directoryServers.put(lsh.getServerId(), lsh);
-      }
-    }
-  }
-
-  /**
-   * Process message of a remote server changing his status.
-   * @param csMsg The message containing the new status
-   * @return The new server status of the DS
-   */
-  public ServerStatus processNewStatus(ChangeStatusMsg csMsg)
-  {
-
-    // Sanity check
-    if (!serverIsLDAPserver)
-    {
-      Message msg =
-        ERR_RECEIVED_CHANGE_STATUS_NOT_FROM_DS.get(baseDn.toString(),
-        Short.toString(serverId), csMsg.toString());
-      logError(msg);
-      return ServerStatus.INVALID_STATUS;
-    }
-
-    // Get the status the DS just entered
-    ServerStatus reqStatus = csMsg.getNewStatus();
-    // Translate new status to a state machine event
-    StatusMachineEvent event = StatusMachineEvent.statusToEvent(reqStatus);
-    if (event == StatusMachineEvent.INVALID_EVENT)
-    {
-      Message msg = ERR_RS_INVALID_NEW_STATUS.get(reqStatus.toString(),
-        baseDn.toString(), Short.toString(serverId));
-      logError(msg);
-      return ServerStatus.INVALID_STATUS;
-    }
-
-    // Check state machine allows this new status
-    ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
-    if (newStatus == ServerStatus.INVALID_STATUS)
-    {
-      Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(baseDn.toString(),
-        Short.toString(serverId), status.toString(), event.toString());
-      logError(msg);
-      return ServerStatus.INVALID_STATUS;
-    }
-
-    status = newStatus;
-
-    return status;
-  }
-
-  /**
-   * Change the status according to the event generated from the status
-   * analyzer.
-   * @param event The event to be used for new status computation
-   * @return The new status of the DS
-   * @throws IOException When raised by the underlying session
-   */
-  public ServerStatus changeStatusFromStatusAnalyzer(StatusMachineEvent event)
-    throws IOException
-  {
-    // Check state machine allows this new status (Sanity check)
-    ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
-    if (newStatus == ServerStatus.INVALID_STATUS)
-    {
-      Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(baseDn.toString(),
-        Short.toString(serverId), status.toString(), event.toString());
-      logError(msg);
-      // Status analyzer must only change from NORMAL_STATUS to DEGRADED_STATUS
-      // and vice versa. We may are being trying to change the status while for
-      // instance another status has just been entered: e.g a full update has
-      // just been engaged. In that case, just ignore attempt to change the
-      // status
-      return newStatus;
-    }
-
-    // Send message requesting to change the DS status
-    ChangeStatusMsg csMsg = new ChangeStatusMsg(newStatus,
-      ServerStatus.INVALID_STATUS);
-
-    if (debugEnabled())
-    {
-      TRACER.debugInfo(
-        "In RS " +
-        replicationServerDomain.getReplicationServer().getServerId() +
-        " Sending change status from status analyzer to " + getServerId() +
-        " for baseDn " + baseDn + ":\n" + csMsg);
-    }
-
-    session.publish(csMsg);
-
-    status = newStatus;
-
-    return newStatus;
-  }
-
-  /**
-   * When this handler is connected to a replication server, specifies if
-   * a wanted server is connected to this replication server.
-   *
-   * @param wantedServer The server we want to know if it is connected
-   * to the replication server represented by this handler.
-   * @return boolean True is the wanted server is connected to the server
-   * represented by this handler.
-   */
-  public boolean isRemoteLDAPServer(short wantedServer)
-  {
-    synchronized (directoryServers)
-    {
-      for (LightweightServerHandler server : directoryServers.values())
-      {
-        if (wantedServer == server.getServerId())
-        {
-          return true;
-        }
-      }
-      return false;
-    }
-  }
-
-  /**
-   * When the handler is connected to a replication server, specifies the
-   * replication server has remote LDAP servers connected to it.
-   *
-   * @return boolean True is the replication server has remote LDAP servers
-   * connected to it.
-   */
-  public boolean hasRemoteLDAPServers()
-  {
-    synchronized (directoryServers)
-    {
-      return !directoryServers.isEmpty();
-    }
-  }
-
-  /**
-   * Send an InitializeRequestMessage to the server connected through this
-   * handler.
-   *
-   * @param msg The message to be processed
-   * @throws IOException when raised by the underlying session
-   */
-  public void send(RoutableMsg msg) throws IOException
-  {
-    if (debugEnabled())
       TRACER.debugInfo("In " +
         replicationServerDomain.getReplicationServer().
-        getMonitorInstanceName() +
-        " SH for remote server " + this.getMonitorInstanceName() + ":" +
-        "\nsends message:\n" + msg);
-    session.publish(msg);
-  }
-
-  /**
-   * Send an ErrorMsg to the peer.
-   *
-   * @param errorMsg The message to be sent
-   * @throws IOException when raised by the underlying session
-   */
-  public void sendError(ErrorMsg errorMsg) throws IOException
-  {
-    session.publish(errorMsg);
-  }
-
-  /**
-   * Process the reception of a WindowProbeMsg message.
-   *
-   * @param  windowProbeMsg The message to process.
-   *
-   * @throws IOException    When the session becomes unavailable.
-   */
-  public void process(WindowProbeMsg windowProbeMsg) throws IOException
-  {
-    if (rcvWindow > 0)
-    {
-      // The LDAP server believes that its window is closed
-      // while it is not, this means that some problem happened in the
-      // window exchange procedure !
-      // lets update the LDAP server with out current window size and hope
-      // that everything will work better in the futur.
-      // TODO also log an error message.
-      WindowMsg msg = new WindowMsg(rcvWindow);
-      session.publish(msg);
-    } else
-    {
-      // Both the LDAP server and the replication server believes that the
-      // window is closed. Lets check the flowcontrol in case we
-      // can now resume operations and send a windowMessage if necessary.
-      checkWindow();
+        getMonitorInstanceName() + ", " +
+        this.getClass().getSimpleName() + " " + this + ":" +
+        "\nSH START HANDSHAKE RECEIVED:\n" + inStartMsg.toString()+
+        "\nAND REPLIED:\n" + outStartMsg.toString());
     }
   }
 
   /**
-   * Returns the value of generationId for that handler.
-   * @return The value of the generationId.
+   * Log the messages involved in the start handshake.
+   * @param outStartMsg The message sent first.
+   * @param inStartMsg The message received in response.
    */
-  public long getGenerationId()
+  protected void logStartHandshakeSNDandRCV(
+      StartMsg outStartMsg,
+      StartMsg inStartMsg)
   {
-    return generationId;
-  }
-
-  /**
-   * 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 forwardGenerationIdToRS(ResetGenerationIdMsg msg)
-    throws IOException
-  {
-    session.publish(msg);
-  }
-
-  /**
-   * Set a new generation ID.
-   *
-   * @param generationId The new generation ID
-   *
-   */
-  public void setGenerationId(long generationId)
-  {
-    this.generationId = generationId;
-  }
-
-  /**
-   * Returns the Replication Server Domain to which belongs this server handler.
-   *
-   * @return The replication server domain.
-   */
-  public ReplicationServerDomain getDomain()
-  {
-    return this.replicationServerDomain;
-  }
-
-  /**
-   * Return a Set containing the servers known by this replicationServer.
-   * @return a set containing the servers known by this replicationServer.
-   */
-  public Set<Short> getConnectedDirectoryServerIds()
-  {
-    synchronized (directoryServers)
-    {
-      return directoryServers.keySet();
-    }
-  }
-
-  /**
-   * Order the peer DS server to change his status or close the connection
-   * according to the requested new generation id.
-   * @param newGenId The new generation id to take into account
-   * @throws IOException If IO error occurred.
-   */
-  public void changeStatusForResetGenId(long newGenId)
-    throws IOException
-  {
-    StatusMachineEvent event = null;
-
-    if (newGenId == -1)
-    {
-      // The generation id is being made invalid, let's put the DS
-      // into BAD_GEN_ID_STATUS
-      event = StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT;
-    } else
-    {
-      if (newGenId == generationId)
-      {
-        if (status == ServerStatus.BAD_GEN_ID_STATUS)
-        {
-          // This server has the good new reference generation id.
-          // Close connection with him to force his reconnection: DS will
-          // reconnect in NORMAL_STATUS or DEGRADED_STATUS.
-
-          if (debugEnabled())
-          {
-            TRACER.debugInfo(
-              "In RS " +
-              replicationServerDomain.getReplicationServer().getServerId() +
-              ". Closing connection to DS " + getServerId() +
-              " for baseDn " + baseDn + " to force reconnection as new local" +
-              " generation id and remote one match and DS is in bad gen id: " +
-              newGenId);
-          }
-
-          // Connection closure must not be done calling RSD.stopHandler() as it
-          // would rewait the RSD lock that we already must have entering this
-          // method. This would lead to a reentrant lock which we do not want.
-          // So simply close the session, this will make the hang up appear
-          // after the reader thread that took the RSD lock realeases it.
-          try
-          {
-            if (session != null)
-              session.close();
-          } catch (IOException e)
-          {
-            // ignore
-          }
-
-          // NOT_CONNECTED_STATUS is the last one in RS session life: handler
-          // will soon disappear after this method call...
-          status = ServerStatus.NOT_CONNECTED_STATUS;
-          return;
-        } else
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugInfo(
-              "In RS " +
-              replicationServerDomain.getReplicationServer().getServerId() +
-              ". DS " + getServerId() + " for baseDn " + baseDn +
-              " has already generation id " + newGenId +
-              " so no ChangeStatusMsg sent to him.");
-          }
-          return;
-        }
-      } else
-      {
-        // This server has a bad generation id compared to new reference one,
-        // let's put it into BAD_GEN_ID_STATUS
-        event = StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT;
-      }
-    }
-
-    if ((event == StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT) &&
-      (status == ServerStatus.FULL_UPDATE_STATUS))
-    {
-      // Prevent useless error message (full update status cannot lead to bad
-      // gen status)
-      Message message = NOTE_BAD_GEN_ID_IN_FULL_UPDATE.get(
-        Short.toString(replicationServerDomain.
-        getReplicationServer().getServerId()),
-        baseDn.toString(),
-        Short.toString(serverId),
-        Long.toString(generationId),
-        Long.toString(newGenId));
-      logError(message);
-      return;
-    }
-
-    ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
-
-    if (newStatus == ServerStatus.INVALID_STATUS)
-    {
-      Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(baseDn.toString(),
-        Short.toString(serverId), status.toString(), event.toString());
-      logError(msg);
-      return;
-    }
-
-    // Send message requesting to change the DS status
-    ChangeStatusMsg csMsg = new ChangeStatusMsg(newStatus,
-      ServerStatus.INVALID_STATUS);
-
     if (debugEnabled())
     {
-      TRACER.debugInfo(
-        "In RS " +
-        replicationServerDomain.getReplicationServer().getServerId() +
-        " Sending change status for reset gen id to " + getServerId() +
-        " for baseDn " + baseDn + ":\n" + csMsg);
+      TRACER.debugInfo("In " +
+        replicationServerDomain.getReplicationServer().
+        getMonitorInstanceName() + ", " +
+        this.getClass().getSimpleName() + " " + this + ":" +
+        "\nSH START HANDSHAKE SENT("+ this +
+        "):\n" + outStartMsg.toString()+
+        "\nAND RECEIVED:\n" + inStartMsg.toString());
     }
-
-    session.publish(csMsg);
-
-    status = newStatus;
   }
 
   /**
-   * Set the shut down flag to true and returns the previous value of the flag.
-   * @return The previous value of the shut down flag
+   * Log the messages involved in the Topology handshake.
+   * @param inTopoMsg The message received first.
+   * @param outTopoMsg The message sent in response.
    */
-  public boolean engageShutdown()
+  protected void logTopoHandshakeRCVandSND(
+      TopologyMsg inTopoMsg,
+      TopologyMsg outTopoMsg)
   {
-    // Use thread safe boolean
-    return shuttingDown.getAndSet(true);
-  }
-
-  /**
-   * Gets the status of the connected DS.
-   * @return The status of the connected DS.
-   */
-  public ServerStatus getStatus()
-  {
-    return status;
-  }
-
-  /**
-   * Gets the protocol version used with this remote server.
-   * @return The protocol version used with this remote server.
-   */
-  public short getProtocolVersion()
-  {
-    return protocolVersion;
-  }
-
-  /**
-   * Add the DSinfos of the connected Directory Servers
-   * to the List of DSInfo provided as a parameter.
-   *
-   * @param dsInfos The List of DSInfo that should be updated
-   *                with the DSInfo for the directoryServers
-   *                connected to this ServerHandler.
-   */
-  public void addDSInfos(List<DSInfo> dsInfos)
-  {
-    synchronized (directoryServers)
+    if (debugEnabled())
     {
-      for (LightweightServerHandler ls : directoryServers.values())
-      {
-        dsInfos.add(ls.toDSInfo());
-      }
+      TRACER.debugInfo("In " +
+          replicationServerDomain.getReplicationServer().
+          getMonitorInstanceName() + ", " +
+          this.getClass().getSimpleName() + " " + this + ":" +
+          "\nSH TOPO HANDSHAKE RECEIVED:\n" + inTopoMsg.toString() +
+          "\nAND REPLIED:\n" + outTopoMsg.toString());
     }
   }
 
   /**
-   * Gets the group id of the server represented by this object.
-   * @return The group id of the server represented by this object.
+   * Log the messages involved in the Topology handshake.
+   * @param outTopoMsg The message sent first.
+   * @param inTopoMsg The message received in response.
    */
-  public byte getGroupId()
+  protected void logTopoHandshakeSNDandRCV(
+      TopologyMsg outTopoMsg,
+      TopologyMsg inTopoMsg)
   {
-    return groupId;
+    if (debugEnabled())
+    {
+      TRACER.debugInfo("In " +
+          replicationServerDomain.getReplicationServer().
+          getMonitorInstanceName() + ", " +
+          this.getClass().getSimpleName() + " " + this + ":" +
+          "\nSH TOPO HANDSHAKE SENT:\n" + outTopoMsg.toString() +
+          "\nAND RECEIVED:\n" + inTopoMsg.toString());
+    }
   }
+
+  /**
+   * Log the messages involved in the Topology/StartSession handshake.
+   * @param inStartSessionMsg The message received first.
+   * @param outTopoMsg The message sent in response.
+   */
+  protected void logStartSessionHandshake(
+      StartSessionMsg inStartSessionMsg,
+      TopologyMsg outTopoMsg)
+  {
+    if (debugEnabled())
+    {
+      TRACER.debugInfo("In " +
+          replicationServerDomain.getReplicationServer().
+          getMonitorInstanceName() + ", " +
+          this.getClass().getSimpleName() + " " + this + " :" +
+          "\nSH SESSION HANDSHAKE RECEIVED:\n" +
+          "\nAND REPLIED:\n" + outTopoMsg.toString());
+    }
+  }
+
+  /**
+   * Log the messages involved in the Topology/StartSession handshake.
+   * @param inStartECLSessionMsg The message received first.
+   */
+  protected void logStartECLSessionHandshake(
+      StartECLSessionMsg inStartECLSessionMsg)
+  {
+    if (debugEnabled())
+    {
+      TRACER.debugInfo("In " +
+          replicationServerDomain.getReplicationServer().
+          getMonitorInstanceName() + ", " +
+          this.getClass().getSimpleName() + " " + this + " :" +
+          "\nSH SESSION HANDSHAKE RECEIVED:\n" +
+          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 bea8995..c128db2 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
@@ -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;
 
@@ -93,8 +93,9 @@
     ServerHandler handler,
     ReplicationServerDomain replicationServerDomain)
   {
-    super("Replication Reader for " + handler.toString() + " in RS " +
-      replicationServerDomain.getReplicationServer().getServerId());
+    super("Replication Reader Thread for handler of " +
+        handler.toString() +
+        " in " + replicationServerDomain);
     this.session = session;
     this.serverId = serverId;
     this.handler = handler;
@@ -106,13 +107,10 @@
    */
   public void run()
   {
+    Message errMessage = null;
     if (debugEnabled())
     {
-      TRACER.debugInfo(
-        "In RS " + replicationServerDomain.getReplicationServer().
-        getMonitorInstanceName() +
-        (handler.isReplicationServer() ? " RS " : " LS") +
-        " reader starting for serverId=" + serverId);
+      TRACER.debugInfo(this.getName() + " starting");
     }
     /*
      * wait on input stream
@@ -127,16 +125,11 @@
         {
           ReplicationMsg msg = session.receive();
 
-          /*
           if (debugEnabled())
           {
-          TRACER.debugInfo(
-          "In RS " + replicationServerDomain.getReplicationServer().
-          getMonitorInstanceName() +
-          (handler.isReplicationServer()?" From RS ":" From LS")+
-          " with serverId=" + serverId + " receives " + msg);
+            TRACER.debugInfo(this.getName() + " receives " + msg);
           }
-           */
+
           if (msg instanceof AckMsg)
           {
             AckMsg ack = (AckMsg) msg;
@@ -146,7 +139,7 @@
           {
             boolean filtered = false;
             /* Ignore updates in some cases */
-            if (handler.isLDAPserver())
+            if (handler.isDataServer())
             {
               /**
                * Ignore updates from DS in bad BAD_GENID_STATUS or
@@ -250,12 +243,36 @@
           } else if (msg instanceof TopologyMsg)
           {
             TopologyMsg topoMsg = (TopologyMsg) msg;
-            replicationServerDomain.receiveTopoInfoFromRS(topoMsg,
-              handler, true);
+            try
+            {
+              ReplicationServerHandler rsh = (ReplicationServerHandler)handler;
+              replicationServerDomain.receiveTopoInfoFromRS(topoMsg,
+                  rsh, true);
+            }
+            catch(Exception e)
+            {
+              errMessage =
+                ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE.get(
+                    "TopologyMsg", "other");
+              logError(errMessage);
+            }
           } else if (msg instanceof ChangeStatusMsg)
           {
             ChangeStatusMsg csMsg = (ChangeStatusMsg) msg;
-            replicationServerDomain.processNewStatus(handler, csMsg);
+            try
+            {
+              DataServerHandler dsh = (DataServerHandler)handler;
+              replicationServerDomain.processNewStatus(dsh, csMsg);
+            }
+            catch(Exception e)
+            {
+              errMessage =
+                ERR_RECEIVED_CHANGE_STATUS_NOT_FROM_DS.get(
+                    replicationServerDomain.getBaseDn(),
+                    Short.toString(handler.getServerId()),
+                    csMsg.toString());
+              logError(errMessage);
+            }
           } else if (msg instanceof MonitorRequestMsg)
           {
             MonitorRequestMsg replServerMonitorRequestMsg =
@@ -271,8 +288,8 @@
              * The remote server has sent an unknown message,
              * close the conenction.
              */
-            Message message = NOTE_READER_NULL_MSG.get(handler.toString());
-            logError(message);
+            errMessage = NOTE_READER_NULL_MSG.get(handler.toString());
+            logError(errMessage);
             return;
           }
         } catch (NotSupportedOldVersionPDUException e)
@@ -286,7 +303,8 @@
               getMonitorInstanceName() + ":" + e.getMessage());
         }
       }
-    } catch (IOException e)
+    }
+    catch (IOException e)
     {
       /*
        * The connection has been broken
@@ -297,13 +315,15 @@
         TRACER.debugInfo(
           "In RS " + replicationServerDomain.getReplicationServer().
           getMonitorInstanceName() +
-          " reader IO EXCEPTION for serverID=" + serverId +
+          " reader IO EXCEPTION for serverID=" + serverId + " " +
+          this + " " +
           stackTraceToSingleLineString(e) + " " + e.getLocalizedMessage());
-      Message message = NOTE_SERVER_DISCONNECT.get(handler.toString(),
+      errMessage = NOTE_SERVER_DISCONNECT.get(handler.toString(),
         Short.toString(replicationServerDomain.
         getReplicationServer().getServerId()));
-      logError(message);
-    } catch (ClassNotFoundException e)
+      logError(errMessage);
+    }
+    catch (ClassNotFoundException e)
     {
       if (debugEnabled())
         TRACER.debugInfo(
@@ -315,49 +335,48 @@
        * The remote server has sent an unknown message,
        * close the connection.
        */
-      Message message = ERR_UNKNOWN_MESSAGE.get(handler.toString());
-      logError(message);
-    } catch (Exception e)
+      errMessage = ERR_UNKNOWN_MESSAGE.get(handler.toString());
+      logError(errMessage);
+    }
+    catch (Exception e)
     {
       if (debugEnabled())
         TRACER.debugInfo(
           "In RS <" + replicationServerDomain.getReplicationServer().
           getMonitorInstanceName() +
           " server reader EXCEPTION serverID=" + serverId +
-          stackTraceToSingleLineString(e));
+          " " + stackTraceToSingleLineString(e));
       /*
        * The remote server has sent an unknown message,
        * close the connection.
        */
-      Message message = NOTE_READER_EXCEPTION.get(handler.toString());
-      logError(message);
-    } finally
+      errMessage = NOTE_READER_EXCEPTION.get(handler.toString());
+      logError(errMessage);
+    }
+    finally
     {
       /*
        * The thread only exit the loop above is some error condition
        * happen.
        * Attempt to close the socket and stop the server handler.
        */
-      if (debugEnabled())
-        TRACER.debugInfo(
-          "In RS " + replicationServerDomain.getReplicationServer().
-          getMonitorInstanceName() +
-          " server reader for serverID=" + serverId +
-          " is closing the session");
       try
       {
+        if (debugEnabled())
+          TRACER.debugInfo(
+            "In RS " + replicationServerDomain.getReplicationServer().
+            getMonitorInstanceName() +
+            this + " is closing the session");
         session.close();
       } catch (IOException e)
       {
       // ignore
       }
       replicationServerDomain.stopServer(handler);
+      if (debugEnabled())
+      {
+        TRACER.debugInfo(this.getName() + " stopped " + errMessage);
+      }
     }
-    if (debugEnabled())
-      TRACER.debugInfo(
-        "In RS " + replicationServerDomain.getReplicationServer().
-        getMonitorInstanceName() +
-        (handler.isReplicationServer() ? " RS" : " LDAP") +
-        " server reader stopped for serverID=" + serverId);
   }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.java
index 90492e8..340983a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.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;
 import org.opends.messages.Message;
@@ -58,7 +58,6 @@
   private ProtocolSession session;
   private ServerHandler handler;
   private ReplicationServerDomain replicationServerDomain;
-  private short serverId;
   private short protocolVersion = -1;
 
   /**
@@ -76,10 +75,10 @@
                       ServerHandler handler,
                       ReplicationServerDomain replicationServerDomain)
   {
-    super("Replication Writer for " + handler.toString() + " in RS " +
-      replicationServerDomain.getReplicationServer().getServerId());
+    super("Replication Writer Thread for handler of " +
+        handler.toString() +
+        " in " + replicationServerDomain);
 
-    this.serverId = serverId;
     this.session = session;
     this.handler = handler;
     this.replicationServerDomain = replicationServerDomain;
@@ -94,16 +93,10 @@
    */
   public void run()
   {
+    Message errMessage = null;
     if (debugEnabled())
     {
-      if (handler.isReplicationServer())
-      {
-        TRACER.debugInfo("Replication server writer starting " + serverId);
-      }
-      else
-      {
-        TRACER.debugInfo("LDAP server writer starting " + serverId);
-      }
+      TRACER.debugInfo(this.getName() + " starting");
     }
     try
     {
@@ -111,10 +104,14 @@
       {
         UpdateMsg update = replicationServerDomain.take(this.handler);
         if (update == null)
+        {
+          errMessage = Message.raw(
+           "Connection closure: null update returned by domain.");
           return;       /* this connection is closing */
+        }
 
         /* Ignore updates in some cases */
-        if (handler.isLDAPserver())
+        if (handler.isDataServer())
         {
           /**
            * Ignore updates to DS in bad BAD_GENID_STATUS or FULL_UPDATE_STATUS
@@ -184,6 +181,7 @@
             ", writer to " + this.handler.getMonitorInstanceName() +
             " publishes msg=[" + update.toString() + "]"+
             " refgenId=" + referenceGenerationId +
+            " isAssured=" + update.isAssured() +
             " server=" + handler.getServerId() +
             " generationId=" + handler.getGenerationId());
         }
@@ -200,10 +198,10 @@
        * The remote host has disconnected and this particular Tree is going to
        * be removed, just ignore the exception and let the thread die as well
        */
-      Message message = NOTE_SERVER_DISCONNECT.get(handler.toString(),
+      errMessage = NOTE_SERVER_DISCONNECT.get(handler.toString(),
         Short.toString(replicationServerDomain.
         getReplicationServer().getServerId()));
-      logError(message);
+      logError(errMessage);
     }
     catch (SocketException e)
     {
@@ -211,10 +209,10 @@
        * The remote host has disconnected and this particular Tree is going to
        * be removed, just ignore the exception and let the thread die as well
        */
-      Message message = NOTE_SERVER_DISCONNECT.get(handler.toString(),
+      errMessage = NOTE_SERVER_DISCONNECT.get(handler.toString(),
         Short.toString(replicationServerDomain.
         getReplicationServer().getServerId()));
-      logError(message);
+      logError(errMessage);
     }
     catch (Exception e)
     {
@@ -222,9 +220,9 @@
        * An unexpected error happened.
        * Log an error and close the connection.
        */
-      Message message = ERR_WRITER_UNEXPECTED_EXCEPTION.get(handler.toString() +
+      errMessage = ERR_WRITER_UNEXPECTED_EXCEPTION.get(handler.toString() +
                         " " +  stackTraceToSingleLineString(e));
-      logError(message);
+      logError(errMessage);
     }
     finally {
       try
@@ -235,17 +233,9 @@
        // Can't do much more : ignore
       }
       replicationServerDomain.stopServer(handler);
-
       if (debugEnabled())
       {
-        if (handler.isReplicationServer())
-        {
-          TRACER.debugInfo("Replication server writer stopping " + serverId);
-        }
-        else
-        {
-          TRACER.debugInfo("LDAP server writer stopping " + serverId);
-        }
+        TRACER.debugInfo(this.getName() + " stopped " + errMessage);
       }
     }
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java
index 0353276..4b9cd70 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java
@@ -22,14 +22,13 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2008 Sun Microsystems, Inc.
+ *      Copyright 2008-2009 Sun Microsystems, Inc.
  */
 package org.opends.server.replication.server;
 
 import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
 import static org.opends.server.loggers.debug.DebugLogger.getTracer;
 
-
 import org.opends.server.api.DirectoryThread;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.common.ServerStatus;
@@ -115,7 +114,7 @@
       // Go through each connected DS, get the number of pending changes we have
       // for it and change status accordingly if threshold value is
       // crossed/uncrossed
-      for (ServerHandler serverHandler :
+      for (DataServerHandler serverHandler :
         replicationServerDomain.getConnectedDSs(). values())
       {
         // Get number of pending changes for this server
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 c349289..a53ca20 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
@@ -26,15 +26,9 @@
  */
 package org.opends.server.replication.service;
 
-import org.opends.server.replication.protocol.HeartbeatMonitor;
-
-import org.opends.messages.*;
-
-import static org.opends.server.loggers.ErrorLogger.logError;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-
-import org.opends.server.loggers.debug.DebugTracer;
 import static org.opends.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.IOException;
@@ -52,13 +46,33 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.messages.Severity;
 import org.opends.server.api.DirectoryThread;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.replication.common.ChangeNumber;
 import org.opends.server.replication.common.DSInfo;
 import org.opends.server.replication.common.RSInfo;
 import org.opends.server.replication.common.ServerState;
 import org.opends.server.replication.common.ServerStatus;
-import org.opends.server.replication.protocol.*;
+import org.opends.server.replication.protocol.ChangeStatusMsg;
+import org.opends.server.replication.protocol.HeartbeatMonitor;
+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.ReplSessionSecurity;
+import org.opends.server.replication.protocol.ReplicationMsg;
+import org.opends.server.replication.protocol.ServerStartECLMsg;
+import org.opends.server.replication.protocol.ServerStartMsg;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
+import org.opends.server.replication.protocol.StartSessionMsg;
+import org.opends.server.replication.protocol.TopologyMsg;
+import org.opends.server.replication.protocol.UpdateMsg;
+import org.opends.server.replication.protocol.WindowMsg;
+import org.opends.server.replication.protocol.WindowProbeMsg;
+import org.opends.server.util.ServerConstants;
 import org.opends.server.replication.server.ReplicationServer;
 
 /**
@@ -300,6 +314,43 @@
     }
   }
 
+  private void connect()
+  {
+    if (this.baseDn.compareToIgnoreCase(
+        ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT)==0)
+    {
+      connectAsECL();
+    }
+    else
+    {
+      connectAsDataServer();
+    }
+  }
+
+  /**
+   * Special aspects of connecting as ECL compared to connecting as data server
+   * are :
+   * - 1 single RS configured
+   * - so no choice of the prefered RS
+   * - No same groupID polling
+   * - ?? Heartbeat
+   * - Start handshake is :
+   *    Broker ---> StartECLMsg       ---> RS
+   *          <---- ReplServerStartMsg ---
+   *           ---> StartSessionECLMsg --> RS
+   */
+  private void connectAsECL()
+  {
+    // FIXME:ECL List of RS to connect is for now limited to one RS only
+    String bestServer = this.servers.iterator().next();
+
+    ReplServerStartMsg inReplServerStartMsg
+      = performECLPhaseOneHandshake(bestServer, true);
+
+    if (inReplServerStartMsg!=null)
+      performECLPhaseTwoHandshake(bestServer);
+  }
+
   /**
    * Connect to a ReplicationServer.
    *
@@ -325,7 +376,7 @@
    *
    * @throws NumberFormatException address was invalid
    */
-  private void connect()
+  private void connectAsDataServer()
   {
     HashMap<String, ServerInfo> rsInfos = new HashMap<String, ServerInfo>();
 
@@ -350,6 +401,9 @@
        * Connect to each replication server and get their ServerState then find
        * out which one is the best to connect to.
        */
+      if (debugEnabled())
+        TRACER.debugInfo("phase 1 : will perform PhaseOneH with each RS in " +
+            " order to elect the prefered one");
       for (String server : servers)
       {
         // Connect to server and get reply message
@@ -375,6 +429,9 @@
           serverId, baseDn, groupId);
 
         // Best found, now initialize connection to this one (handshake phase 1)
+        if (debugEnabled())
+          TRACER.debugInfo(
+              "phase 2 : will perform PhaseOneH with the prefered RS.");
         replServerStartMsg = performPhaseOneHandshake(bestServer, true);
 
         if (replServerStartMsg != null) // Handshake phase 1 exchange went well
@@ -764,6 +821,8 @@
       {
         try
         {
+          if (debugEnabled())
+            TRACER.debugInfo("In RB, closing session after phase 1");
           localSession.close();
         } catch (IOException e)
         {
@@ -789,6 +848,225 @@
   }
 
   /**
+   * Connect to the provided server performing the first phase handshake
+   * (start messages exchange) and return the reply message from the replication
+   * server.
+   *
+   * @param server Server to connect to.
+   * @param keepConnection Do we keep session opened or not after handshake.
+   *        Use true if want to perform handshake phase 2 with the same session
+   *        and keep the session to create as the current one.
+   * @return The ReplServerStartMsg the server replied. Null if could not
+   *         get an answer.
+   */
+  private ReplServerStartMsg performECLPhaseOneHandshake(String server,
+    boolean keepConnection)
+  {
+    ReplServerStartMsg replServerStartMsg = null;
+
+    // Parse server string.
+    int separator = server.lastIndexOf(':');
+    String port = server.substring(separator + 1);
+    String hostname = server.substring(0, separator);
+    ProtocolSession localSession = null;
+
+    boolean error = false;
+    try
+    {
+      /*
+       * Open a socket connection to the next candidate.
+       */
+      int intPort = Integer.parseInt(port);
+      InetSocketAddress serverAddr = new InetSocketAddress(
+        InetAddress.getByName(hostname), intPort);
+      if (keepConnection)
+        tmpReadableServerName = serverAddr.toString();
+      Socket socket = new Socket();
+      socket.setReceiveBufferSize(1000000);
+      socket.setTcpNoDelay(true);
+      socket.connect(serverAddr, 500);
+      localSession = replSessionSecurity.createClientSession(server, socket,
+        ReplSessionSecurity.HANDSHAKE_TIMEOUT);
+      boolean isSslEncryption =
+        replSessionSecurity.isSslEncryption(server);
+
+      // Send our start msg.
+      ServerStartECLMsg serverStartECLMsg = new ServerStartECLMsg(
+          baseDn, 0, 0, 0, 0,
+          maxRcvWindow, heartbeatInterval, state,
+          ProtocolVersion.getCurrentVersion(), this.getGenerationID(),
+          isSslEncryption,
+          groupId);
+      localSession.publish(serverStartECLMsg);
+
+      // Read the ReplServerStartMsg that should come back.
+      replServerStartMsg = (ReplServerStartMsg) localSession.receive();
+
+      if (debugEnabled())
+      {
+        TRACER.debugInfo("In RB for " + baseDn +
+          "\nRB HANDSHAKE SENT:\n" + serverStartECLMsg.toString() +
+          "\nAND RECEIVED:\n" + replServerStartMsg.toString());
+      }
+
+      // Sanity check
+      String repDn = replServerStartMsg.getBaseDn();
+      if (!(this.baseDn.equals(repDn)))
+      {
+        Message message = ERR_DS_DN_DOES_NOT_MATCH.get(repDn.toString(),
+          this.baseDn);
+        logError(message);
+        error = true;
+      }
+
+      /*
+       * We have sent our own protocol version to the replication server.
+       * The replication server will use the same one (or an older one
+       * if it is an old replication server).
+       */
+      if (keepConnection)
+        protocolVersion = ProtocolVersion.minWithCurrent(
+          replServerStartMsg.getVersion());
+
+      if (!isSslEncryption)
+      {
+        localSession.stopEncryption();
+      }
+    } catch (ConnectException e)
+    {
+      /*
+       * There was no server waiting on this host:port
+       * Log a notice and try the next replicationServer in the list
+       */
+      if (!connectionError)
+      {
+        Message message = NOTE_NO_CHANGELOG_SERVER_LISTENING.get(server);
+        if (keepConnection) // Log error message only for final connection
+        {
+          // the error message is only logged once to avoid overflowing
+          // the error log
+          logError(message);
+        } else if (debugEnabled())
+        {
+          TRACER.debugInfo(message.toString());
+        }
+      }
+      error = true;
+    } catch (Exception e)
+    {
+      if ( (e instanceof SocketTimeoutException) && debugEnabled() )
+      {
+        TRACER.debugInfo("Timeout trying to connect to RS " + server +
+          " for dn: " + baseDn);
+      }
+      Message message = ERR_EXCEPTION_STARTING_SESSION_PHASE.get("1",
+        baseDn, server, e.getLocalizedMessage() +
+        stackTraceToSingleLineString(e));
+      if (keepConnection) // Log error message only for final connection
+      {
+        logError(message);
+      } else if (debugEnabled())
+      {
+        TRACER.debugInfo(message.toString());
+      }
+      error = true;
+    }
+
+    // Close session if requested
+    if (!keepConnection || error)
+    {
+      if (localSession != null)
+      {
+        try
+        {
+          if (debugEnabled())
+            TRACER.debugInfo("In RB, closing session after phase 1");
+          localSession.close();
+        } catch (IOException e)
+        {
+          // The session was already closed, just ignore.
+        }
+        localSession = null;
+      }
+      if (error)
+      {
+        replServerStartMsg = null;
+      } // Be sure to return null.
+
+    }
+
+    // If this connection as the one to use for sending and receiving updates,
+    // store it.
+    if (keepConnection)
+    {
+      session = localSession;
+    }
+
+    return replServerStartMsg;
+  }
+
+  /**
+   * Performs the second phase handshake (send StartSessionMsg and receive
+   * TopologyMsg messages exchange) and return the reply message from the
+   * replication server.
+   *
+   * @param server Server we are connecting with.
+   * @param initStatus The status we are starting with
+   * @return The ReplServerStartMsg the server replied. Null if could not
+   *         get an answer.
+   */
+  private TopologyMsg performECLPhaseTwoHandshake(String server)
+  {
+    TopologyMsg topologyMsg = null;
+
+    try
+    {
+      // Send our Start Session
+      StartECLSessionMsg startECLSessionMsg = null;
+      startECLSessionMsg = new StartECLSessionMsg();
+      startECLSessionMsg.setOperationId(Short.toString(serverId));
+      session.publish(startECLSessionMsg);
+
+      /* FIXME:ECL In the handshake phase two, should RS send back a topo msg ?
+       * Read the TopologyMsg that should come back.
+      topologyMsg = (TopologyMsg) session.receive();
+       */
+      if (debugEnabled())
+      {
+        TRACER.debugInfo("In RB for " + baseDn +
+          "\nRB HANDSHAKE SENT:\n" + startECLSessionMsg.toString());
+      //  +   "\nAND RECEIVED:\n" + topologyMsg.toString());
+      }
+
+      // Alright set the timeout to the desired value
+      session.setSoTimeout(timeout);
+      connected = true;
+
+    } catch (Exception e)
+    {
+      Message message = ERR_EXCEPTION_STARTING_SESSION_PHASE.get("2",
+        baseDn, server, e.getLocalizedMessage() +
+        stackTraceToSingleLineString(e));
+      logError(message);
+
+      if (session != null)
+      {
+        try
+        {
+          session.close();
+        } catch (IOException ex)
+        {
+          // The session was already closed, just ignore.
+        }
+        session = null;
+      }
+      // Be sure to return null.
+      topologyMsg = null;
+    }
+    return topologyMsg;
+  }
+
+  /**
    * Performs the second phase handshake (send StartSessionMsg and receive
    * TopologyMsg messages exchange) and return the reply message from the
    * replication server.
@@ -1720,6 +1998,12 @@
     {
       boolean done = false;
 
+      if (debugEnabled())
+      {
+        TRACER.debugInfo("SameGroupIdPoller for: " + baseDn.toString() +
+          " started.");
+      }
+
       while ((!done) && (!sameGroupIdPollershutdown))
       {
         // Sleep some time between checks
@@ -1850,11 +2134,6 @@
    */
   public void receiveTopo(TopologyMsg topoMsg)
   {
-
-    if (debugEnabled())
-      TRACER.debugInfo("Replication domain " + baseDn
-        + " received topology info update:\n" + topoMsg);
-
     // Store new lists
     synchronized(getDsList())
     {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
index db00003..7230cb6 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -747,6 +747,12 @@
   public static final String OC_GROUP_OF_URLS_LC = "groupofurls";
 
 
+  /**
+   * The name of the objectclass that will be used as the structural class for
+   * monitor entries.
+   */
+  public static final String OC_CHANGELOG_ENTRY = "changeLogEntry";
+
 
   /**
    * The request OID for the cancel extended operation.
@@ -1961,6 +1967,13 @@
        "1.3.6.1.4.1.42.2.27.9.5.8";
 
 
+  /**
+   *  The OID for the entry change request control.
+   *  FIXME:ECL ask for OID_ECL_REQUEST_CONTROL
+   */
+  public static final String OID_ECL_COOKIE_EXCHANGE_CONTROL =
+    "1.3.6.1.4.1.26027.1.5.4";
+
 
   /**
    * The IANA-assigned OID for the feature allowing a user to request that all
@@ -2983,5 +2996,12 @@
    * The normalized false value.
    */
   public static final ByteString FALSE_VALUE = ByteString.valueOf("FALSE");
+
+  /**
+   * The root Dn for the external change log.
+   */
+  public static final String DN_EXTERNAL_CHANGELOG_ROOT = "cn=changelog";
+
+
 }
 
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
new file mode 100644
index 0000000..a711d46
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
@@ -0,0 +1,1300 @@
+/*
+ * 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 2008-2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.workflowelement.externalchangelog;
+
+
+
+import static org.opends.messages.CoreMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.util.StaticUtils.needsBase64Encoding;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+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;
+
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.Severity;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.plugin.PluginResult;
+import org.opends.server.controls.EntryChangelogNotificationControl;
+import org.opends.server.controls.ExternalChangelogRequestControl;
+import org.opends.server.controls.LDAPAssertionRequestControl;
+import org.opends.server.controls.MatchedValuesControl;
+import org.opends.server.controls.PersistentSearchControl;
+import org.opends.server.controls.ProxiedAuthV1Control;
+import org.opends.server.controls.ProxiedAuthV2Control;
+import org.opends.server.core.AccessControlConfigManager;
+import org.opends.server.core.AddOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.PersistentSearch;
+import org.opends.server.core.PluginConfigManager;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.core.SearchOperationWrapper;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.ExternalChangeLogSession;
+import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.replication.plugin.MultimasterReplication;
+import org.opends.server.replication.protocol.AddMsg;
+import org.opends.server.replication.protocol.DeleteMsg;
+import org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.replication.protocol.ModifyDNMsg;
+import org.opends.server.replication.protocol.ModifyMsg;
+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;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.CanceledOperationException;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ObjectClass;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.RawAttribute;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchScope;
+import org.opends.server.types.operation.PostOperationSearchOperation;
+import org.opends.server.types.operation.PreOperationSearchOperation;
+import org.opends.server.types.operation.SearchEntrySearchOperation;
+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;
+
+
+
+/**
+ * This class defines an operation used to search for entries in a local backend
+ * of the Directory Server.
+ */
+public class ECLSearchOperation
+       extends SearchOperationWrapper
+       implements PreOperationSearchOperation, PostOperationSearchOperation,
+                  SearchEntrySearchOperation, SearchReferenceSearchOperation
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  /**
+   * The ECL Start Session we'll send to the RS.
+   */
+  private StartECLSessionMsg startECLSessionMsg;
+
+  // The set of objectclasses that will be used in ECL entries.
+  private static HashMap<ObjectClass,String> eclObjectClasses;
+
+  // 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;
+
+  /**
+   * The base DN for the search.
+   */
+  protected DN baseDN;
+
+  /**
+   * The persistent search request, if applicable.
+   */
+  protected PersistentSearch persistentSearch;
+
+  /**
+   * The filter for the search.
+   */
+  protected SearchFilter filter;
+
+  private ExternalChangeLogSession eclSession;
+
+  // The set of supported controls for this WE
+  private HashSet<String> supportedControls;
+
+  // The set of supported features for this WE
+  private HashSet<String> supportedFeatures;
+
+  String privateDomainsBaseDN;
+
+  /**
+   * Creates a new operation that may be used to search for entries in a local
+   * backend of the Directory Server.
+   *
+   * @param  search  The operation to process.
+   */
+  public ECLSearchOperation(SearchOperation search)
+  {
+    super(search);
+
+    try
+    {
+      rootBaseDN = DN.decode(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
+    }
+    catch (Exception e){}
+
+    // Construct the set of objectclasses to include in the base monitor entry.
+    eclObjectClasses = new LinkedHashMap<ObjectClass,String>(2);
+    ObjectClass topOC = DirectoryServer.getObjectClass(OC_TOP, true);
+    eclObjectClasses.put(topOC, OC_TOP);
+    ObjectClass eclEntryOC = DirectoryServer.getObjectClass(OC_CHANGELOG_ENTRY,
+                                                           true);
+    eclObjectClasses.put(eclEntryOC, OC_CHANGELOG_ENTRY);
+
+
+    // Define an empty sets for the supported controls and features.
+    supportedControls = new HashSet<String>(0);
+    supportedControls.add(ServerConstants.OID_SERVER_SIDE_SORT_REQUEST_CONTROL);
+    supportedControls.add(ServerConstants.OID_VLV_REQUEST_CONTROL);
+    supportedFeatures = new HashSet<String>(0);
+
+    ECLWorkflowElement.attachLocalOperation(search, this);
+  }
+
+
+
+  /**
+   * Process this search operation against a local backend.
+   *
+   * @param wfe
+   *          The local backend work-flow element.
+   * @throws CanceledOperationException
+   *           if this operation should be cancelled
+   */
+  public void processECLSearch(ECLWorkflowElement wfe)
+      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:
+    {
+      // 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.
+      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;
+      }
+
+      // Check for a request to cancel this operation.
+      checkIfCanceled(false);
+
+      // Invoke the pre-operation search plugins.
+      executePostOpPlugins = true;
+      PluginResult.PreOperation preOpResult =
+          pluginConfigManager.invokePreOperationSearchPlugins(this);
+      if (!preOpResult.continueProcessing())
+      {
+        setResultCode(preOpResult.getResultCode());
+        appendErrorMessage(preOpResult.getErrorMessage());
+        setMatchedDN(preOpResult.getMatchedDN());
+        setReferralURLs(preOpResult.getReferralURLs());
+        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.
+      setResultCode(ResultCode.SUCCESS);
+
+
+      // If there's a persistent search, then register it with the server.
+      if (persistentSearch != null)
+      {
+        wfe.registerPersistentSearch(persistentSearch);
+        persistentSearch.enable();
+      }
+
+      // Process the search.
+      try
+      {
+        processSearch();
+      }
+      catch (DirectoryException de)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+
+        setResponseData(de);
+
+        if (persistentSearch != null)
+        {
+          persistentSearch.cancel();
+          setSendResponse(true);
+        }
+
+        break searchProcessing;
+      }
+      catch (CanceledOperationException coe)
+      {
+        if (persistentSearch != null)
+        {
+          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)));
+
+        if (persistentSearch != null)
+        {
+          persistentSearch.cancel();
+          setSendResponse(true);
+        }
+
+        break searchProcessing;
+      }
+    }
+
+
+    // Check for a request to cancel this operation.
+    checkIfCanceled(false);
+
+    // Invoke the post-operation search plugins.
+    if (executePostOpPlugins)
+    {
+      PluginResult.PostOperation postOpResult =
+           pluginConfigManager.invokePostOperationSearchPlugins(this);
+      if (!postOpResult.continueProcessing())
+      {
+        setResultCode(postOpResult.getResultCode());
+        appendErrorMessage(postOpResult.getErrorMessage());
+        setMatchedDN(postOpResult.getMatchedDN());
+        setReferralURLs(postOpResult.getReferralURLs());
+      }
+    }
+  }
+
+
+  /**
+   * Handles any controls contained in the request.
+   *
+   * @throws  DirectoryException  If there is a problem with any of the request
+   *                              controls.
+   */
+  protected void handleRequestControls()
+          throws DirectoryException
+  {
+    List<Control> requestControls  = getRequestControls();
+    if ((requestControls != null) && (! requestControls.isEmpty()))
+    {
+      for (int i=0; i < requestControls.size(); i++)
+      {
+        Control c   = requestControls.get(i);
+        String  oid = c.getOID();
+        if (! AccessControlConfigManager.getInstance().
+                   getAccessControlHandler().isAllowed(baseDN, this, c))
+        {
+          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
+        }
+
+        if (oid.equals(OID_ECL_COOKIE_EXCHANGE_CONTROL))
+        {
+          ExternalChangelogRequestControl eclControl =
+            getRequestControl(ExternalChangelogRequestControl.DECODER);
+          this.requestCookie = eclControl.getCookie();
+        }
+        else if (oid.equals(OID_LDAP_ASSERTION))
+        {
+          LDAPAssertionRequestControl assertControl =
+                getRequestControl(LDAPAssertionRequestControl.DECODER);
+
+          try
+          {
+            // FIXME -- We need to determine whether the current user has
+            //          permission to make this determination.
+            SearchFilter assertionFilter = assertControl.getSearchFilter();
+            Entry entry;
+            try
+            {
+              entry = DirectoryServer.getEntry(baseDN);
+            }
+            catch (DirectoryException de)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, de);
+              }
+
+              throw new DirectoryException(de.getResultCode(),
+                             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());
+            }
+
+            if (! assertionFilter.matchesEntry(entry))
+            {
+              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
+                                           ERR_SEARCH_ASSERTION_FAILED.get());
+            }
+          }
+          catch (DirectoryException de)
+          {
+            if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
+            {
+              throw de;
+            }
+
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            }
+
+            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
+                           ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
+                                de.getMessageObject()), de);
+          }
+        }
+        else if (oid.equals(OID_PROXIED_AUTH_V1))
+        {
+          // The requester must have the PROXIED_AUTH privilige in order to be
+          // able to use this control.
+          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+          {
+            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+          }
+
+          ProxiedAuthV1Control proxyControl =
+              getRequestControl(ProxiedAuthV1Control.DECODER);
+
+          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
+          setAuthorizationEntry(authorizationEntry);
+          if (authorizationEntry == null)
+          {
+            setProxiedAuthorizationDN(DN.nullDN());
+          }
+          else
+          {
+            setProxiedAuthorizationDN(authorizationEntry.getDN());
+          }
+        }
+        else if (oid.equals(OID_PROXIED_AUTH_V2))
+        {
+          // The requester must have the PROXIED_AUTH privilige in order to be
+          // able to use this control.
+          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+          {
+            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+          }
+
+          ProxiedAuthV2Control proxyControl =
+              getRequestControl(ProxiedAuthV2Control.DECODER);
+
+          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
+          setAuthorizationEntry(authorizationEntry);
+          if (authorizationEntry == null)
+          {
+            setProxiedAuthorizationDN(DN.nullDN());
+          }
+          else
+          {
+            setProxiedAuthorizationDN(authorizationEntry.getDN());
+          }
+        }
+        else if (oid.equals(OID_PERSISTENT_SEARCH))
+        {
+          PersistentSearchControl psearchControl =
+            getRequestControl(PersistentSearchControl.DECODER);
+
+          persistentSearch = new PersistentSearch(this,
+                                      psearchControl.getChangeTypes(),
+                                      psearchControl.getReturnECs());
+
+          // If we're only interested in changes, then we don't actually want
+          // to process the search now.
+          if (psearchControl.getChangesOnly())
+          {
+            changesOnly = true;
+          }
+        }
+        else if (oid.equals(OID_LDAP_SUBENTRIES))
+        {
+          setReturnLDAPSubentries(true);
+        }
+        else if (oid.equals(OID_MATCHED_VALUES))
+        {
+          MatchedValuesControl matchedValuesControl =
+                getRequestControl(MatchedValuesControl.DECODER);
+          setMatchedValuesControl(matchedValuesControl);
+        }
+        else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
+        {
+          setIncludeUsableControl(true);
+        }
+        else if (oid.equals(OID_REAL_ATTRS_ONLY))
+        {
+          setRealAttributesOnly(true);
+        }
+        else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
+        {
+          setVirtualAttributesOnly(true);
+        }
+        else if (oid.equals(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.
+
+        else if (c.isCritical())
+        {
+          if ((replicationServer == null) || (! supportsControl(oid)))
+          {
+            throw new DirectoryException(
+                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
+                           ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
+          }
+        }
+      }
+    }
+  }
+
+  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 (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
+    eclSession = replicationServer.createECLSession(startECLSessionMsg);
+
+    if (!getScope().equals(SearchScope.SINGLE_LEVEL))
+    {
+      // Root entry
+      Entry entry = createRootEntry();
+      if (matchFilter(entry))
+        returnEntry(entry, null);
+    }
+
+    if (true)
+    {
+      // Loop on result entries
+      int INITIAL=0;
+      int PSEARCH=1;
+      int phase=INITIAL;
+      while (true)
+      {
+
+        // Check for a request to cancel this operation.
+        checkIfCanceled(false);
+
+        ECLUpdateMsg update = eclSession.getNextUpdate();
+        if (update!=null)
+        {
+          if (phase==INITIAL)
+            buildAndReturnEntry(update);
+        }
+        else
+        {
+          if (phase==INITIAL)
+          {
+            if (this.persistentSearch == null)
+            {
+              eclSession.close();
+              break;
+            }
+            else
+            {
+              phase=PSEARCH;
+              break;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  private boolean supportsControl(String oid)
+  {
+    return ((supportedControls != null) &&
+        supportedControls.contains(oid));
+  }
+
+  /**
+   * Build an ECL entry from a provided ECL msg and return it.
+   * @param eclmsg The provided ECL msg.
+   * @throws DirectoryException When an errors occurs.
+   */
+  private void buildAndReturnEntry(ECLUpdateMsg eclmsg)
+  throws DirectoryException
+  {
+    Entry entry = null;
+
+    // build and filter
+    entry = createEntryFromMsg(eclmsg);
+    if (matchFilter(entry))
+    {
+      List<Control> controls = new ArrayList<Control>(0);
+
+      EntryChangelogNotificationControl clrc
+      = new EntryChangelogNotificationControl(
+          true,eclmsg.getCookie().toString());
+      controls.add(clrc);
+      boolean entryReturned = returnEntry(entry, controls);
+      if (entryReturned==false)
+      {
+        // FIXME:ECL Handle the error case where returnEntry fails
+      }
+    }
+  }
+
+  /**
+   * Test if the provided entry matches the filter, base and scope.
+   * @param  entry The provided entry
+   * @return whether the entry matches.
+   * @throws DirectoryException When a problem occurs.
+   */
+  private boolean matchFilter(Entry entry)
+  throws DirectoryException
+  {
+    boolean ms = entry.matchesBaseAndScope(getBaseDN(), getScope());
+    boolean mf = getFilter().matchesEntry(entry);
+    return (ms && mf);
+  }
+
+  /**
+   * Create an ECL entry from a provided ECL msg.
+   *
+   * @param  eclmsg the provided ECL msg.
+   * @return        the created ECL entry.
+   * @throws DirectoryException When an error occurs.
+   */
+  public static Entry createEntryFromMsg(ECLUpdateMsg eclmsg)
+  throws DirectoryException
+  {
+    Entry clEntry = null;
+
+    // Get the meat fro the ecl msg
+    UpdateMsg msg = eclmsg.getUpdateMsg();
+
+    if (msg instanceof AddMsg)
+    {
+      AddMsg addMsg = (AddMsg)msg;
+
+      // Map the addMsg to an LDIF string for the 'changes' attribute
+      String LDIFchanges = addMsgToLDIFString(addMsg);
+
+      clEntry = createChangelogEntry(
+          eclmsg.getServiceId(),
+          eclmsg.getCookie().toString(),
+          DN.decode(addMsg.getDn()),
+          addMsg.getChangeNumber(),
+          LDIFchanges, // entry as created (in LDIF format)
+          addMsg.getUniqueId(),
+          null, // real time current entry
+          null, // real time attrs names
+          null, // hist entry attributes
+          0,    // TODO:ECL G Good changelog draft compat. addMsg.getSeqnum()
+      "add");
+
+    } else
+    if (msg instanceof ModifyMsg)
+    {
+      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();
+        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
+            0,    // TODO:ECL G Good changelog draft compat. modMsg.getSeqnum()
+        "modify");
+
+      }
+      catch(Exception e)
+      {
+        // FIXME:ECL Handle error when createOperation raise DataFormatExceptin
+      }
+    }
+    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
+          0,    // 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>();
+      String delAttrs = delMsgToLDIFString(rattributes);
+
+      clEntry = createChangelogEntry(
+          eclmsg.getServiceId(),
+          eclmsg.getCookie().toString(),
+          DN.decode(delMsg.getDn()),
+          delMsg.getChangeNumber(),
+          delAttrs,
+          delMsg.getUniqueId(),
+          null,
+          null,
+          null, //rattributes,
+          0, // TODO:ECL G Good changelog draft compat. delMsg.getSeqnum()
+         "delete");
+    }
+    return clEntry;
+  }
+
+  /**
+   * Creates the root entry of the external changelog.
+   * @return The root entry created.
+   */
+  private Entry createRootEntry()
+  {
+    HashMap<ObjectClass,String> oclasses =
+      new LinkedHashMap<ObjectClass,String>(3);
+    oclasses.putAll(eclObjectClasses);
+
+    HashMap<AttributeType,List<Attribute>> userAttrs =
+      new LinkedHashMap<AttributeType,List<Attribute>>();
+
+    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;
+  }
+
+  /**
+   * Create an ECL entry.
+   * @param serviceID       The provided cookie value.
+   * @param cookie          The provided cookie value.
+   * @param targetDN        The provided targetDN.
+   * @param changeNumber    The provided replication changeNumber.
+   * @param clearLDIFchanges     The provided LDIF changes for ADD and MODIFY
+   * @param targetUUID      The provided targetUUID.
+   * @param entry           The provided related current entry.
+   * @param targetAttrNames The provided list of attributes names that should
+   *                        be read from the entry (real time values)
+   * @param histEntryAttributes TODO:ECL Adress hist entry attributes
+   * @param seqnum          The provided RCL int change number.
+   * @param changetype      The provided change type (add, ...)
+   * @return                The created ECL entry.
+   * @throws DirectoryException
+   *         When any error occurs.
+   */
+  public static Entry createChangelogEntry(
+      String serviceID,
+      String cookie,
+      DN targetDN,
+      ChangeNumber changeNumber,
+      String clearLDIFchanges,
+      String targetUUID,
+      Entry entry,
+      List<String> targetAttrNames,
+      List<RawAttribute> histEntryAttributes,
+      int seqnum,
+      String changetype)
+  throws DirectoryException
+  {
+    String dnString = "cn="+ changeNumber +"," +
+      serviceID + "," +
+      ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT;
+
+    HashMap<ObjectClass,String> oClasses =
+      new LinkedHashMap<ObjectClass,String>(3);
+    oClasses.putAll(eclObjectClasses);
+
+    ObjectClass extensibleObjectOC =
+      DirectoryServer.getObjectClass(OC_EXTENSIBLE_OBJECT_LC, true);
+    oClasses.put(extensibleObjectOC, OC_EXTENSIBLE_OBJECT);
+
+    HashMap<AttributeType,List<Attribute>> uAttrs =
+      new LinkedHashMap<AttributeType,List<Attribute>>();
+
+    HashMap<AttributeType,List<Attribute>> operationalAttrs =
+      new LinkedHashMap<AttributeType,List<Attribute>>();
+
+    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+
+    // TODO:ECL G Good changelog compat.
+    if (seqnum>0)
+    {
+      Attribute a = Attributes.create("changeNumber", String.valueOf(seqnum));
+      attrList = new ArrayList<Attribute>(1);
+      attrList.add(a);
+      uAttrs.put(a.getAttributeType(), attrList);
+    }
+
+    //
+    Attribute a = Attributes.create("replicationCSN", changeNumber.toString());
+    attrList = new ArrayList<Attribute>(1);
+    attrList.add(a);
+    uAttrs.put(a.getAttributeType(), attrList);
+
+    //
+    SimpleDateFormat dateFormat;
+    dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
+    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); // ??
+    a = Attributes.create(
+        DirectoryServer.getAttributeType("changeTime", true),
+        dateFormat.format(new Date(changeNumber.getTime())));
+    attrList = new ArrayList<Attribute>(1);
+    attrList.add(a);
+    uAttrs.put(a.getAttributeType(), attrList);
+
+    /* Change time in a friendly format
+    Date date = new Date(changeNumber.getTime());
+    a = Attributes.create("clearChangeTime", date.toString());
+    attrList = new ArrayList<Attribute>(1);
+    attrList.add(a);
+    uAttrs.put(a.getAttributeType(), attrList);
+    */
+
+    //
+    a = Attributes.create("changeType", changetype);
+    attrList = new ArrayList<Attribute>(1);
+    attrList.add(a);
+    uAttrs.put(a.getAttributeType(), attrList);
+
+    //
+    a = Attributes.create(
+        DirectoryServer.getAttributeType("targetdn", true),
+        targetDN.toNormalizedString());
+    attrList = new ArrayList<Attribute>(1);
+    attrList.add(a);
+    uAttrs.put(a.getAttributeType(), attrList);
+
+    //
+    a = Attributes.create("replicaIdentifier",
+        Short.toString(changeNumber.getServerId()));
+    attrList = new ArrayList<Attribute>(1);
+    attrList.add(a);
+    uAttrs.put(a.getAttributeType(), attrList);
+
+    if (clearLDIFchanges != null)
+    {
+      if (changetype.equalsIgnoreCase("delete"))
+      {
+        a = Attributes.create("clearDeletedEntryAttrs", clearLDIFchanges);
+      }
+      attrList = new ArrayList<Attribute>(1);
+      attrList.add(a);
+      operationalAttrs.put(a.getAttributeType(), attrList);
+
+      if (changetype.equalsIgnoreCase("delete"))
+      {
+        a = Attributes.create("deletedentryattrs",
+            clearLDIFchanges + "\n"); // force base64
+      }
+      else
+      {
+        AttributeType at = DirectoryServer.getAttributeType("changes");
+        a = Attributes.create(at, clearLDIFchanges + "\n"); // force base64
+      }
+      attrList = new ArrayList<Attribute>(1);
+      attrList.add(a);
+      uAttrs.put(a.getAttributeType(), attrList);
+    }
+
+    a = Attributes.create("targetentryuuid", targetUUID);
+    attrList = new ArrayList<Attribute>(1);
+    attrList.add(a);
+    operationalAttrs.put(a.getAttributeType(), attrList);
+
+    a = Attributes.create("cookie", cookie);
+    attrList = new ArrayList<Attribute>(1);
+    attrList.add(a);
+    operationalAttrs.put(a.getAttributeType(), attrList);
+
+    // entryAttribute version
+    /*
+    if (targetAttrNames != null)
+    {
+      String sEntryAttrs = null;
+      for (String attrName : targetAttrNames)
+      {
+        List<Attribute> attrs = entry.getAttribute(attrName);
+        for (Attribute attr : attrs)
+        {
+          if (sEntryAttrs==null)
+            sEntryAttrs="";
+          else
+            sEntryAttrs+=";";
+          sEntryAttrs += attr.toString();
+        }
+      }
+      if (sEntryAttrs!=null)
+      {
+        a = Attributes.create("entryAttributes", sEntryAttrs);
+        attrList = new ArrayList<Attribute>(1);
+        attrList.add(a);
+        uAttrs.put(a.getAttributeType(), attrList);
+      }
+    }
+    */
+
+    /*
+    if (targetAttrNames != null)
+    {
+      for (String attrName : targetAttrNames)
+      {
+        String newName = "target"+attrName;
+        List<Attribute> attrs = entry.getAttribute(attrName);
+        for (Attribute aa : attrs)
+        {
+          AttributeBuilder builder = new AttributeBuilder(
+            DirectoryServer.getDefaultAttributeType(newName));
+          builder.setOptions(aa.getOptions());
+          builder.addAll(aa);
+          attrList = new ArrayList<Attribute>(1);
+          attrList.add(builder.toAttribute());
+          uAttrs.put(aa.getAttributeType(), attrList);
+        }
+      }
+    }
+    */
+    /* TODO: Implement entry attributes historical values
+    if (histEntryAttributes != null)
+    {
+      for (RawAttribute rea : histEntryAttributes)
+      {
+        // uAttrs.put(ea.getAttributeType(), null);
+        // FIXME: ERRONEOUS TYPING !!!!!!!!!!!!!
+        try
+        {
+          String newName = "target"+rea.getAttributeType();
+          AttributeType nat=DirectoryServer.getDefaultAttributeType(newName);
+          rea.setAttributeType(newName);
+          attrList = new ArrayList<Attribute>(1);
+          attrList.add(rea.toAttribute());
+          uAttrs.put(nat, attrList);
+        }
+        catch(Exception e)
+        {
+
+        }
+      }
+    }
+    */
+
+    // at the end build the CL entry to be returned
+    Entry cle = new Entry(
+        DN.decode(dnString),
+        eclObjectClasses,
+        uAttrs,
+        operationalAttrs);
+
+    return cle;
+  }
+
+  private static String addMsgToLDIFString(AddMsg addMsg)
+  {
+    StringBuilder modTypeLine = new StringBuilder();
+    // LinkedList<StringBuilder> ldifLines =
+    //  new LinkedList<StringBuilder>();
+
+    try
+    {
+      AddOperation addOperation = (AddOperation)addMsg.createOperation(
+          InternalClientConnection.getRootConnection());
+
+      Map<AttributeType,List<Attribute>> attributes =
+        new HashMap<AttributeType,List<Attribute>>();
+
+      for (RawAttribute a : addOperation.getRawAttributes())
+      {
+        Attribute attr = a.toAttribute();
+        AttributeType attrType = attr.getAttributeType();
+        List<Attribute> attrs = attributes.get(attrType);
+        if (attrs == null)
+        {
+          attrs = new ArrayList<Attribute>(1);
+          attrs.add(attr);
+          attributes.put(attrType, attrs);
+        }
+        else
+        {
+          attrs.add(attr);
+        }
+      }
+      for (List<Attribute> attrList : attributes.values())
+      {
+        for (Attribute a : attrList)
+        {
+          StringBuilder attrName = new StringBuilder(a.getName());
+          for (String o : a.getOptions())
+          {
+            attrName.append(";");
+            attrName.append(o);
+          }
+          for (AttributeValue av : a)
+          {
+            String stringValue = av.toString();
+
+            modTypeLine.append(attrName);
+            if (needsBase64Encoding(stringValue))
+            {
+              modTypeLine.append(":: ");
+              modTypeLine.append(Base64.encode(av.getValue()));
+            }
+            else
+            {
+              modTypeLine.append(": ");
+              modTypeLine.append(stringValue);
+            }
+            modTypeLine.append("\n");
+          }
+        }
+      }
+      return modTypeLine.toString();
+    }
+    catch(Exception e)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, e);
+    }
+    return null;
+  }
+
+  /**
+   * q.
+   * @param rattributes q.
+   * @return
+   */
+  private static String delMsgToLDIFString(ArrayList<RawAttribute> rattributes)
+  {
+    StringBuilder modTypeLine = new StringBuilder();
+    try
+    {
+
+      Map<AttributeType,List<Attribute>> attributes =
+        new HashMap<AttributeType,List<Attribute>>();
+
+      for (RawAttribute a : rattributes)
+      {
+        Attribute attr = a.toAttribute();
+        AttributeType attrType = attr.getAttributeType();
+        List<Attribute> attrs = attributes.get(attrType);
+        if (attrs == null)
+        {
+          attrs = new ArrayList<Attribute>(1);
+          attrs.add(attr);
+          attributes.put(attrType, attrs);
+        }
+        else
+        {
+          attrs.add(attr);
+        }
+      }
+
+      for (List<Attribute> attrList : attributes.values())
+      {
+        for (Attribute a : attrList)
+        {
+          StringBuilder attrName = new StringBuilder(a.getName());
+          for (String o : a.getOptions())
+          {
+            attrName.append(";");
+            attrName.append(o);
+          }
+          for (AttributeValue av : a)
+          {
+            // ??
+            String stringValue = av.toString();
+            modTypeLine.append(attrName);
+            if (needsBase64Encoding(stringValue))
+            {
+              modTypeLine.append(":: ");
+              modTypeLine.append(Base64.encode(av.getValue()));
+            }
+            else
+            {
+              modTypeLine.append(": ");
+              modTypeLine.append(stringValue);
+            }
+            modTypeLine.append("\n");
+          }
+        }
+      }
+      return modTypeLine.toString();
+    }
+    catch(Exception e)
+    {
+      TRACER.debugCaught(DebugLogLevel.ERROR, e);
+    }
+    return null;
+  }
+
+  /**
+   * Dumps a list of modifications into an LDIF string.
+   * @param mods The provided list of modifications.
+   * @return The LDIF string.
+   */
+  public static String modToLDIF(List<Modification> mods)
+  {
+    StringBuilder modTypeLine = new StringBuilder();
+    Iterator<Modification> iterator = mods.iterator();
+    while (iterator.hasNext())
+    {
+      Modification m = iterator.next();
+      Attribute a = m.getAttribute();
+      String attrName = a.getName();
+      modTypeLine.append(m.getModificationType().getLDIFName());
+      modTypeLine.append(": ");
+      modTypeLine.append(attrName);
+      modTypeLine.append("\n");
+
+      Iterator<AttributeValue> iteratorValues = a.iterator();
+      while (iteratorValues.hasNext())
+      {
+        AttributeValue av = iteratorValues.next();
+
+        String stringValue = av.toString();
+
+        modTypeLine.append(attrName);
+        if (needsBase64Encoding(stringValue))
+        {
+          modTypeLine.append(":: ");
+          modTypeLine.append(Base64.encode(av.getValue()));
+        }
+        else
+        {
+          modTypeLine.append(": ");
+          modTypeLine.append(stringValue);
+        }
+        modTypeLine.append("\n");
+      }
+
+      modTypeLine.append("-");
+      if (iterator.hasNext())
+      {
+        modTypeLine.append("\n");
+      }
+    }
+    return modTypeLine.toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult cancel(CancelRequest cancelRequest)
+  {
+    if (eclSession!=null)
+    {
+      try
+      {
+        eclSession.close();
+      }
+      catch(Exception e){}
+    }
+    return super.cancel(cancelRequest);
+  }
+}
+
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
new file mode 100644
index 0000000..8581738
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
@@ -0,0 +1,220 @@
+/*
+ * 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.workflowelement.externalchangelog;
+
+
+
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.opends.server.admin.std.server.WorkflowElementCfg;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.PersistentSearch;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.server.ReplicationServer;
+import org.opends.server.types.CanceledOperationException;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Operation;
+import org.opends.server.workflowelement.LeafWorkflowElement;
+
+
+
+
+/**
+ * This class defines a workflow element for the external changelog (ECL);
+ * e-g an entity that handles the processing of an operation against the ECL.
+ */
+public class ECLWorkflowElement extends
+    LeafWorkflowElement<WorkflowElementCfg>
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  /**
+   *The set of persistent searches registered with this work flow element.
+   */
+  private final List<PersistentSearch> persistentSearches =
+    new CopyOnWriteArrayList<PersistentSearch>();
+
+  /**
+   * A string indicating the type of the workflow element.
+   */
+  public static final String ECL_WORKFLOW_ELEMENT = "EXTERNAL CHANGE LOG";
+
+  /**
+   * The replication server object to which we will submits request
+   * on the ECL. Retrieved from the local DirectoryServer.
+   */
+  private ReplicationServer replicationServer;
+
+  /**
+   * Creates a new instance of the External Change Log workflow element.
+   * @param rs the provided replication server
+   * @throws DirectoryException  If the ECL workflow is already registered.
+   */
+  public ECLWorkflowElement(ReplicationServer rs)
+  throws DirectoryException
+  {
+    this.replicationServer =rs;
+    super.initialize(ECL_WORKFLOW_ELEMENT, ECL_WORKFLOW_ELEMENT);
+    DirectoryServer.registerWorkflowElement(this);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void finalizeWorkflowElement()
+  {
+    // null all fields so that any use of the finalized object will raise
+    // an NPE
+    super.initialize(ECL_WORKFLOW_ELEMENT, null);
+
+    // Cancel all persistent searches.
+    for (PersistentSearch psearch : persistentSearches) {
+      psearch.cancel();
+    }
+    persistentSearches.clear();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void execute(Operation operation) throws CanceledOperationException {
+    switch (operation.getOperationType())
+    {
+      case SEARCH:
+        ECLSearchOperation searchOperation =
+             new ECLSearchOperation((SearchOperation) operation);
+        searchOperation.processECLSearch(this);
+        break;
+      case ABANDON:
+        // There is no processing for an abandon operation.
+        break;
+
+      case BIND:
+      case ADD:
+      case DELETE:
+      case MODIFY:
+      case MODIFY_DN:
+      case COMPARE:
+      default:
+        throw new AssertionError("Attempted to execute an invalid operation " +
+                                 "type:  " + operation.getOperationType() +
+                                 " (" + operation + ")");
+    }
+  }
+
+
+
+  /**
+   * Attaches the current local operation to the global operation so that
+   * operation runner can execute local operation post response later on.
+   *
+   * @param <O>              subtype of Operation
+   * @param <L>              subtype of LocalBackendOperation
+   * @param globalOperation  the global operation to which local operation
+   *                         should be attached to
+   * @param currentLocalOperation  the local operation to attach to the global
+   *                               operation
+   */
+  @SuppressWarnings("unchecked")
+  public static final <O extends Operation,L> void
+              attachLocalOperation (O globalOperation, L currentLocalOperation)
+  {
+    List<?> existingAttachment =
+      (List<?>) globalOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
+
+    List<L> newAttachment = new ArrayList<L>();
+
+    if (existingAttachment != null)
+    {
+      // This line raises an unchecked conversion warning.
+      // There is nothing we can do to prevent this warning
+      // so let's get rid of it since we know the cast is safe.
+      newAttachment.addAll ((List<L>) existingAttachment);
+    }
+    newAttachment.add (currentLocalOperation);
+    globalOperation.setAttachment(Operation.LOCALBACKENDOPERATIONS,
+                                  newAttachment);
+  }
+
+  /**
+   * Registers the provided persistent search operation with this
+   * local backend workflow element so that it will be notified of any
+   * add, delete, modify, or modify DN operations that are performed.
+   *
+   * @param persistentSearch
+   *          The persistent search operation to register with this
+   *          local backend workflow element.
+   */
+  void registerPersistentSearch(PersistentSearch persistentSearch)
+  {
+    PersistentSearch.CancellationCallback callback =
+      new PersistentSearch.CancellationCallback()
+    {
+      public void persistentSearchCancelled(PersistentSearch psearch)
+      {
+        psearch.getSearchOperation().cancel(null);
+        persistentSearches.remove(psearch);
+      }
+    };
+
+    persistentSearches.add(persistentSearch);
+    persistentSearch.registerCancellationCallback(callback);
+  }
+
+
+
+  /**
+   * Gets the list of persistent searches currently active against
+   * this local backend workflow element.
+   *
+   * @return The list of persistent searches currently active against
+   *         this local backend workflow element.
+   */
+  public List<PersistentSearch> getPersistentSearches()
+  {
+    return persistentSearches;
+  }
+
+  /**
+   * Returns the associated replication server.
+   * @return the rs.
+   */
+  public ReplicationServer getReplicationServer()
+  {
+    return this.replicationServer;
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/package-info.java b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/package-info.java
new file mode 100644
index 0000000..5ea48f2
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/package-info.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+
+
+/**
+ * This package contains source for the local backend workflow element, which
+ * are used to process operations against data stored in local backend databases
+ * and other repositories that are considered "local".
+ */
+@org.opends.server.types.PublicAPI(
+     stability=org.opends.server.types.StabilityLevel.PRIVATE)
+package org.opends.server.workflowelement.externalchangelog;
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ExternalChangelogControlTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ExternalChangelogControlTest.java
new file mode 100644
index 0000000..62d082d
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ExternalChangelogControlTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.controls;
+
+import static org.opends.server.util.ServerConstants.OID_ECL_COOKIE_EXCHANGE_CONTROL;
+import static org.opends.server.util.ServerConstants.OID_PERSISTENT_SEARCH;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.opends.server.protocols.asn1.ASN1;
+import org.opends.server.protocols.asn1.ASN1Writer;
+import org.opends.server.protocols.ldap.LDAPControl;
+import org.opends.server.protocols.ldap.LDAPReader;
+import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.ByteStringBuilder;
+import org.opends.server.types.DirectoryException;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Test ChangeNumber and ChangeNumberGenerator
+ */
+public class ExternalChangelogControlTest
+    extends ControlsTestCase
+{
+
+  /**
+   * Create values for External Changelog Request Control
+   */
+  @DataProvider(name = "eclRequestControl")
+  public Object[][] createECLRequestControlTest()
+  {
+
+    return new Object[][]
+        {
+        {true,  "" },
+        {false, "o=test:;" },
+        {false, "o=test:000001210b6f21e904b100000002;" },
+        {false, "o=test:000001210b6f21e904b100000001;o=test2:000001210b6f21e904b100000002;" },
+        {false, "o=test:000001210b6f21e904b100000001 000001210b6f21e904b200000001;o=test2:000001210b6f21e904b100000002 000001210b6f21e904b200000002;" },
+        };
+  }
+
+  /**
+   * Test ExternalChangelogRequestControl
+   */
+  @Test(dataProvider = "eclRequestControl")
+  public void checkECLRequestControlTest(boolean critical, String value)
+      throws Exception
+  {
+    // Test contructor
+    MultiDomainServerState mdss = new MultiDomainServerState(value);
+    ExternalChangelogRequestControl eclrc 
+      = new ExternalChangelogRequestControl(critical, mdss);
+    assertNotNull(eclrc);
+    assertEquals(critical, eclrc.isCritical());
+    assertEquals(OID_ECL_COOKIE_EXCHANGE_CONTROL, eclrc.getOID());
+    assertTrue(eclrc.getCookie().equalsTo(mdss));
+
+    // Test encode/decode
+    ByteStringBuilder bsb = new ByteStringBuilder();
+    ASN1Writer writer = ASN1.getWriter(bsb);
+    bsb.clear();
+    eclrc.write(writer);
+    LDAPControl control = LDAPReader.readControl(ASN1.getReader(bsb));
+    eclrc =
+      ExternalChangelogRequestControl.DECODER.decode(control.isCritical(), control.getValue());
+    assertNotNull(eclrc);
+    assertEquals(critical, eclrc.isCritical());
+    assertEquals(OID_ECL_COOKIE_EXCHANGE_CONTROL, eclrc.getOID());
+    assertTrue(eclrc.getCookie().equalsTo(mdss),
+       "Expect:"+value+", Got:"+eclrc.getCookie().toString());
+  }
+}
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 a6fd622..6a692eb 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
@@ -158,11 +158,6 @@
     // This test suite depends on having the schema available.
     TestCaseUtils.startServer();
 
-    // After start of server because not seen if server not started
-    // after server started once, TestCaseUtils.startServer() does nothing.
-    logError(Message.raw(Category.SYNC, Severity.NOTICE,
-        " ##### Calling ReplicationTestCase.setUp ##### "));
-
     // Initialize the test backend (TestCaseUtils.TEST_ROOT_DN_STRING)
     // (in case previous (non replication?) tests were run before...)
     TestCaseUtils.initializeTestBackend(true);
@@ -265,7 +260,60 @@
     return broker;
   }
 
-   /**
+  /**
+   * Open an ECL replicationServer session to the local ReplicationServer 
+  protected ReplicationBroker openECLReplicationSession(
+        int window_size, int port, int timeout, boolean emptyOldChanges,
+        Short serverId)
+  throws Exception, SocketException
+  {
+    ServerState state = new ServerState();
+
+    //if (emptyOldChanges)
+    //   new PersistentServerState(baseDn, serverId, new ServerState());
+
+    ReplicationBroker broker = new ReplicationBroker(null,
+        state, "cn=changelog", serverId, window_size,
+        -1, 100000, getReplSessionSecurity(), (byte)1);
+    ArrayList<String> servers = new ArrayList<String>(1);
+    servers.add("localhost:" + port);
+    broker.start(servers);
+    if (timeout != 0)
+      broker.setSoTimeout(timeout);
+    checkConnection(30, broker, port); // give some time to the broker to connect
+                                       // to the replicationServer.
+    if (emptyOldChanges)
+    {
+      // loop receiving update until there is nothing left
+      // to make sure that message from previous tests have been consumed.
+      try
+      {
+        while (true)
+        {
+          ReplicationMsg rMsg = broker.receive();
+          if (rMsg instanceof ErrorMsg)
+          {
+            ErrorMsg eMsg = (ErrorMsg)rMsg;
+            logError(new MessageBuilder(
+                "ReplicationTestCase/openReplicationSession ").append(
+                " received ErrorMessage when emptying old changes ").append(
+                eMsg.getDetails()).toMessage());
+          }
+        }
+      }
+      catch (Exception e)
+      {
+        logError(new MessageBuilder(
+            "ReplicationTestCase/openReplicationSession ").append(e.getMessage())
+            .append(" when emptying old changes").toMessage());
+      }
+    }
+    return broker;
+  }
+  */
+
+
+  /**
    * Check connection of the provided ds to the
    * replication server. Waits for connection to be ok up to secTimeout seconds
    * before failing.
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
index e2e27bf..96b5209 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2008 Sun Microsystems, Inc.
+ *      Copyright 2008-2009 Sun Microsystems, Inc.
  */
 package org.opends.server.replication;
 
@@ -210,7 +210,7 @@
    * Checks that changes to the schema pushed to the replicationServer
    * are received and correctly replayed by replication plugin.
    */
-  @Test(dependsOnMethods = { "pushSchemaChange" })
+  @Test(enabled=true,dependsOnMethods = { "pushSchemaChange" })
   public void replaySchemaChange() throws Exception
   {
     logError(Message.raw(Category.SYNC, Severity.NOTICE,
@@ -341,5 +341,7 @@
     {
       broker.stop();
     }
+    logError(Message.raw(Category.SYNC, Severity.NOTICE,
+    "Ending replication test : pushSchemaFilesChange "));
   }
 }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/common/ExternalChangeLogTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/common/ExternalChangeLogTest.java
new file mode 100644
index 0000000..1e69126
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/common/ExternalChangeLogTest.java
@@ -0,0 +1,1569 @@
+/*
+ * 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.common;
+
+import static org.opends.server.TestCaseUtils.TEST_ROOT_DN_STRING;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.replication.protocol.OperationContext.SYNCHROCONTEXT;
+import static org.opends.server.util.StaticUtils.createEntry;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.Severity;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.api.Backend;
+import org.opends.server.api.ConnectionHandler;
+import org.opends.server.api.SynchronizationProvider;
+import org.opends.server.backends.MemoryBackend;
+import org.opends.server.config.ConfigException;
+import org.opends.server.controls.ExternalChangelogRequestControl;
+import org.opends.server.controls.PersistentSearchChangeType;
+import org.opends.server.controls.PersistentSearchControl;
+import org.opends.server.core.AddOperationBasis;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyDNOperationBasis;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.plugins.InvocationCounterPlugin;
+import org.opends.server.protocols.asn1.ASN1Exception;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.protocols.ldap.BindRequestProtocolOp;
+import org.opends.server.protocols.ldap.BindResponseProtocolOp;
+import org.opends.server.protocols.ldap.LDAPAttribute;
+import org.opends.server.protocols.ldap.LDAPConnectionHandler;
+import org.opends.server.protocols.ldap.LDAPConstants;
+import org.opends.server.protocols.ldap.LDAPFilter;
+import org.opends.server.protocols.ldap.LDAPMessage;
+import org.opends.server.protocols.ldap.LDAPResultCode;
+import org.opends.server.protocols.ldap.LDAPStatistics;
+import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
+import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
+import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
+import org.opends.server.replication.ReplicationTestCase;
+import org.opends.server.replication.plugin.DomainFakeCfg;
+import org.opends.server.replication.plugin.LDAPReplicationDomain;
+import org.opends.server.replication.plugin.MultimasterReplication;
+import org.opends.server.replication.protocol.AddMsg;
+import org.opends.server.replication.protocol.DeleteMsg;
+import org.opends.server.replication.protocol.DoneMsg;
+import org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.replication.protocol.ModifyDNMsg;
+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.UpdateMsg;
+import org.opends.server.replication.server.ReplServerFakeConfiguration;
+import org.opends.server.replication.server.ReplicationServer;
+import org.opends.server.replication.server.ReplicationServerDomain;
+import org.opends.server.replication.service.ReplicationBroker;
+import org.opends.server.tools.LDAPWriter;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.LDAPException;
+import org.opends.server.types.LDIFExportConfig;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.RDN;
+import org.opends.server.types.ResultCode;
+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.localbackend.LocalBackendModifyDNOperation;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for the replicationServer code.
+ */
+
+public class ExternalChangeLogTest extends ReplicationTestCase
+{
+  // The tracer object for the debug logger
+  private static final DebugTracer TRACER = getTracer();
+
+  // The replicationServer that will be used in this test.
+  private ReplicationServer replicationServer = null;
+
+  // 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";
+
+  // The LDAPStatistics object associated with the LDAP connection handler.
+  protected LDAPStatistics ldapStatistics;
+  
+  /**
+   * Set up the environment for performing the tests in this Class.
+   * Replication
+   *
+   * @throws Exception
+   *           If the environment could not be set up.
+   */
+  @BeforeClass
+  public void setUp() throws Exception
+  {
+    super.setUp();
+
+    // This test suite depends on having the schema available.
+    configure();
+  }
+
+  /**
+   * Utility : configure a replicationServer.
+   */
+  protected void configure() throws Exception
+  {
+    //  Find  a free port for the replicationServer
+    ServerSocket socket = TestCaseUtils.bindFreePort();
+    replicationServerPort = socket.getLocalPort();
+    socket.close();
+
+    /*
+    // Add the replication server to the configuration
+    TestCaseUtils.dsconfig(
+        "create-replication-server",
+        "--provider-name", "Multimaster Synchronization",
+        "--set", "replication-db-directory:" + "externalChangeLogTestConfigureDb",
+        "--set", "replication-port:" + replicationServerPort,
+        "--set", "replication-server-id:71");
+
+    // Retrieves the replicationServer object
+    DirectoryServer.getSynchronizationProviders();
+    for (SynchronizationProvider<?> provider : DirectoryServer
+        .getSynchronizationProviders()) {
+      if (provider instanceof MultimasterReplication) {
+        MultimasterReplication mmp = (MultimasterReplication) provider;
+        ReplicationServerListener list = mmp.getReplicationServerListener();
+        if (list != null) {
+          replicationServer = list.getReplicationServer();
+          if (replicationServer != null) {
+            break;
+          }
+        }
+      }
+    }
+    */
+    ReplServerFakeConfiguration conf1 =
+      new ReplServerFakeConfiguration(
+          replicationServerPort, "ExternalChangeLogTestDb",
+          0, 71, 0, 100, null);
+
+    replicationServer = new ReplicationServer(conf1);;
+    debugInfo("configure", "ReplicationServer created"+replicationServer);
+    
+  }
+
+  /**
+   * Launcher.
+   */
+  @Test(enabled=true)
+  public void ECLReplicationServerTest()
+  {
+    ECLRemoteEmpty();replicationServer.clearDb();
+    ECLDirectEmpty();replicationServer.clearDb();
+    ECLDirectAllOps();replicationServer.clearDb();
+    ECLRemoteNonEmpty();replicationServer.clearDb();
+    ECLDirectMsg();replicationServer.clearDb();
+    ECLDirectPsearch(true);replicationServer.clearDb();
+    ECLDirectPsearch(false);replicationServer.clearDb();
+    ECLPrivateDirectMsg();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
+  }
+
+  //=======================================================
+  // From 3 remote ECL session,
+  // Test DoneMsg is received from 1 suffix on each session.
+  private void ECLRemoteEmpty()
+  {
+    String tn = "ECLRemoteEmpty";
+    debugInfo(tn, "Starting test\n\n");
+
+    ReplicationBroker server1 = null;
+    ReplicationBroker server2 = null;
+    ReplicationBroker server3 = null;
+
+    try 
+    {
+      // Create 3 ECL broker
+      server1 = openReplicationSession(
+          DN.decode("cn=changelog"), (short)1111,
+          100, replicationServerPort, 1000, false);
+      assertTrue(server1.isConnected());
+      server2 = openReplicationSession(
+          DN.decode("cn=changelog"), (short)2222,
+          100, replicationServerPort,1000, false);
+      assertTrue(server2.isConnected());
+      server3 = openReplicationSession(
+          DN.decode("cn=changelog"), (short)3333,
+          100, replicationServerPort,1000, false);
+      assertTrue(server3.isConnected());
+
+      // Test broker1 receives only Done
+      ReplicationMsg msg;
+      int msgc=0;
+      do 
+      {
+        msg = server1.receive();
+        msgc++;
+      }
+      while(!(msg instanceof DoneMsg));
+      assertTrue(msgc==1,
+          "Ending " + tn + " with incorrect message type :" +
+          msg.getClass().getCanonicalName());
+      assertTrue(msg instanceof DoneMsg);        
+
+      // Test broker2 receives only Done
+      msgc=0;
+      do 
+      {
+        msg = server2.receive();
+        msgc++;
+      }
+      while(!(msg instanceof DoneMsg));
+      assertTrue(msgc==1,
+          "Ending " + tn + " with incorrect message type :" +
+          msg.getClass().getCanonicalName());
+
+      // Test broker3 receives only Done
+      msgc=0;
+      do 
+      {
+        msg = server3.receive();
+        msgc++;
+      }
+      while(!(msg instanceof DoneMsg));
+      assertTrue(msgc==1,
+          "Ending " + tn + " with incorrect message type :" +
+          msg.getClass().getCanonicalName());
+
+      server1.stop();
+      server2.stop();
+      server3.stop();
+      sleep(500);
+      debugInfo(tn, "Ending test successfully\n\n");      
+    }
+    catch(Exception e)
+    {
+      debugInfo(tn, "Ending test with exception:"
+          +  stackTraceToSingleLineString(e));      
+      assertTrue(e == null);
+    }
+  }
+  
+  //=======================================================
+  // From 1 remote ECL session,
+  // test simple update to be received from 2 suffixes
+  private void ECLRemoteNonEmpty()
+  {
+    String tn = "ECLRemoteNonEmpty";
+    debugInfo(tn, "Starting test\n\n");
+
+    replicationServer.clearDb();
+
+    // create a broker
+    ReplicationBroker server01 = null;
+    ReplicationBroker server02 = null;
+    ReplicationBroker serverECL = null;
+
+    try 
+    {
+      // create 2 reguler brokers on the 2 suffixes
+      server01 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING), (short) 1201, 
+          100, replicationServerPort,
+          1000, true);
+
+      server02 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING2), (short) 1202, 
+          100, replicationServerPort,
+          1000, true, EMPTY_DN_GENID);
+
+      // create and publish 1 change on each suffix
+      long time = TimeThread.getTime();
+      int ts = 1;
+      ChangeNumber cn1 = new ChangeNumber(time, ts++, (short)1201);
+      DeleteMsg delMsg1 =
+        new DeleteMsg("o=" + tn + "1," + TEST_ROOT_DN_STRING, cn1, "ECLBasicMsg1uid");
+      server01.publish(delMsg1);
+      debugInfo(tn, "publishes:" + delMsg1);
+
+      ChangeNumber cn2 = new ChangeNumber(time, ts++, (short)1202);
+      DeleteMsg delMsg2 =
+        new DeleteMsg("o=" + tn + "2," + TEST_ROOT_DN_STRING2, cn2, "ECLBasicMsg2uid");
+      server02.publish(delMsg2);
+      debugInfo(tn, "publishes:" + delMsg2);
+
+      // wait for the server to take these changes into account
+      sleep(500);
+      
+      // open ECL broker
+      serverECL = openReplicationSession(
+          DN.decode("cn=changelog"), (short)10,
+          100, replicationServerPort, 1000, false);
+      assertTrue(serverECL.isConnected());
+
+      // receive change 1 from suffix 1
+      ReplicationMsg msg;
+      msg = serverECL.receive();
+      ECLUpdateMsg eclu = (ECLUpdateMsg)msg;
+      UpdateMsg u = eclu.getUpdateMsg();
+      debugInfo(tn, "RESULT:" + u.getChangeNumber());
+      assertTrue(u.getChangeNumber().equals(cn1), "RESULT:" + u.getChangeNumber());
+      assertTrue(eclu.getCookie().equalsTo(new MultiDomainServerState(
+          "o=test:"+delMsg1.getChangeNumber()+";")));
+      
+      // receive change 2 from suffix 2
+      msg = serverECL.receive();
+      eclu = (ECLUpdateMsg)msg;
+      u = eclu.getUpdateMsg();
+      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()+";")));
+
+      // receive Done
+      msg = serverECL.receive();
+      debugInfo(tn, "RESULT:" + msg);
+      assertTrue(msg instanceof DoneMsg, "RESULT:" + msg);
+
+      // clean
+      serverECL.stop();
+      server01.stop();
+      server02.stop();      
+      sleep(2000);
+      debugInfo(tn, "Ending test successfully");
+    }
+    catch(Exception e)
+    {
+      debugInfo(tn, "Ending test 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()
+  {
+    String tn = "ECLDirectEmpty";
+    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=*)"));
+
+      debugInfo(tn, "Res code=" + op.getResultCode());
+      debugInfo(tn, "Res OE=" + ResultCode.OPERATIONS_ERROR);
+      // 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,
+          DereferencePolicy.NEVER_DEREF_ALIASES,
+          0, 0,
+          false,
+          LDAPFilter.decode("(objectclass=*)"),
+          new LinkedHashSet<String>(0),
+          getControls(""),
+          null);
+
+      // success
+      assertEquals(
+          op2.getResultCode(), ResultCode.SUCCESS,
+          op2.getErrorMessage().toString());
+
+      // root entry returned
+      assertEquals(op2.getEntriesSent(), 1);
+      debugInfo(tn, "Ending test successfully");
+    }
+    catch(LDAPException e)
+    {
+      debugInfo(tn, "Ending test with exception e="
+          +  stackTraceToSingleLineString(e));      
+      assertTrue(e == null);
+    }
+  }
+
+  private ArrayList<Control> getControls(String cookie)
+  {
+    ExternalChangelogRequestControl control =
+      new ExternalChangelogRequestControl(true, 
+          new MultiDomainServerState(cookie));
+    ArrayList<Control> controls = new ArrayList<Control>(0);
+    controls.add(control);
+    return controls;
+  }
+
+  // Utility - creates an LDIFWriter to dump result entries
+  private static LDIFWriter getLDIFWriter()
+  {
+    LDIFWriter ldifWriter = null;
+    ByteArrayOutputStream stream = new ByteArrayOutputStream();
+    LDIFExportConfig exportConfig = new LDIFExportConfig(stream);
+    try
+    {
+      ldifWriter = new LDIFWriter(exportConfig);
+    }
+    catch (Exception e)
+    {
+      assert(e==null);
+    }
+    return ldifWriter;
+  }
+
+  // Add an entry in the database
+  private void addEntry(Entry entry) throws Exception
+  {
+    AddOperationBasis addOp = new AddOperationBasis(connection,
+        InternalClientConnection.nextOperationID(), InternalClientConnection
+        .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
+        entry.getUserAttributes(), entry.getOperationalAttributes());
+    addOp.setInternalOperation(true);
+    addOp.run();
+    assertEquals(addOp.getResultCode(), ResultCode.SUCCESS,
+        addOp.getErrorMessage().toString() + addOp.getAdditionalLogMessage());
+    assertNotNull(getEntry(entry.getDN(), 1000, true));
+  }
+
+  //=======================================================
+  // From embebbded ECL
+  // Search ECL with 1 domain, public then private backend
+  private void ECLPrivateDirectMsg()
+  {
+    ReplicationServer replServer = null;
+    String tn = "ECLPrivateDirectMsg";
+    debugInfo(tn, "Starting test");
+
+    try
+    {
+      // Initialize a second test 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
+      SortedSet<String> replServers = new TreeSet<String>();
+      replServers.add("localhost:"+replicationServerPort);
+      DomainFakeCfg domainConf =
+        new DomainFakeCfg(baseDn, (short) 1602, replServers);
+      LDAPReplicationDomain domain = MultimasterReplication.createNewDomain(domainConf);
+      SynchronizationProvider replicationPlugin = new MultimasterReplication();
+      replicationPlugin.completeSynchronizationProvider();
+      sleep(1000);
+
+      Entry e = createEntry(baseDn);
+      addEntry(e);
+      sleep(1000);
+
+      ExternalChangelogRequestControl control =
+        new ExternalChangelogRequestControl(true, 
+            new MultiDomainServerState(""));
+      ArrayList<Control> controls = new ArrayList<Control>(0);
+      controls.add(control);
+
+      // search on 'cn=changelog'
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      attributes.add("+");
+      attributes.add("*");
+      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=*)"),
+          attributes,
+          controls,
+          null);
+      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());
+        }
+      }
+      assertEquals(entries.size(),1, "Entries number returned by search");
+      
+      // Same with private backend
+      domain.getBackend().setPrivateBackend(true);
+
+      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=*)"),
+          attributes,
+          controls,
+          null);
+      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);
+
+      if (replicationPlugin != null)
+        DirectoryServer.deregisterSynchronizationProvider(replicationPlugin);
+
+      removeTestBackend2(backend2);
+    }
+    catch(Exception e)
+    {
+      debugInfo(tn, "Ending test ECLDirectMsg with exception:\n"
+          +  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()
+  {
+    String tn = "ECLDirectMsg";
+    debugInfo(tn, "Starting test");
+ 
+    try
+    {
+      // Initialize a second test backend
+      Backend backend2 = initializeTestBackend2(true);
+
+      //
+      LDIFWriter ldifWriter = getLDIFWriter();
+
+      // --
+      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());
+
+      cn = new ChangeNumber(time++, ts++, s2test2.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"3," + TEST_ROOT_DN_STRING2, cn, 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);
+
+      // Changes are :
+      //               s1          s2
+      // o=test       msg1/msg4   
+      // 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("*");
+      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);
+
+      // We expect SUCCESS and the 4 changes
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+          searchOp.getErrorMessage().toString());
+      
+      LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
+      if (entries != null)
+      {
+        int i=0;
+        for (SearchResultEntry entry : entries)
+        {
+          debugInfo(tn, " RESULT entry returned:" + entry.toSingleLineString());
+          ldifWriter.writeEntry(entry);
+          try
+          {
+            if (i++==2)
+              cookie =
+                entry.getAttribute("cookie").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'
+      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);
+
+      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,
+          controls,
+          null);
+      
+      
+      // We expect SUCCESS and the 4th change
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+          searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
+      
+      cookie= "";
+      entries = searchOp.getSearchEntries();
+      if (entries != null)
+      {
+        for (SearchResultEntry entry : entries)
+        {
+          debugInfo(tn, "Result entry=\n" + entry.toLDIFString());
+          ldifWriter.writeEntry(entry);
+          try
+          {
+          cookie =
+            entry.getAttribute("cookie").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);
+
+      cn = new ChangeNumber(time++, ts++, s1test.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"5," + TEST_ROOT_DN_STRING, cn, tn+"uuid5");
+      s1test.publish(delMsg);
+      sleep(500);
+
+      // Changes are :
+      //               s1         s2
+      // o=test       msg1,msg5   msg4
+      // o=test2      msg3        msg2
+
+      control =
+        new ExternalChangelogRequestControl(true, 
+            new MultiDomainServerState(cookie));
+      controls = new ArrayList<Control>(0);
+      controls.add(control);
+
+      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,
+          controls,
+          null);
+
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+          searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
+      
+      cookie= "";
+      entries = searchOp.getSearchEntries();
+      if (entries != null)
+      {
+        for (SearchResultEntry resultEntry : entries)
+        {
+          debugInfo(tn, "Result entry=\n" + resultEntry.toLDIFString());
+          ldifWriter.writeEntry(resultEntry);
+          try
+          {
+          cookie =
+            resultEntry.getAttribute("cookie").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, 
+            new MultiDomainServerState(cookie));
+      controls = new ArrayList<Control>(0);
+      controls.add(control);
+
+      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*,o=test)"),
+          attributes,
+          controls,
+          null);
+      
+      
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+          searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
+      
+      entries = searchOp.getSearchEntries();
+      if (entries != null)
+      {
+        for (SearchResultEntry resultEntry : entries)
+        {
+          debugInfo(tn, "Result entry=\n" + resultEntry.toLDIFString());
+          ldifWriter.writeEntry(resultEntry);
+          try
+          {
+          cookie =
+            resultEntry.getAttribute("cookie").get(0).iterator().next().toString();
+          }
+          catch(NullPointerException e)
+          {}
+        }
+      }
+      assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS);
+
+      // we expect msg1 + msg4 + msg5
+      assertEquals(searchOp.getSearchEntries().size(), 3);
+
+      // --
+      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 of the domain for the first cookie feature
+      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);
+
+      cn = new ChangeNumber(time++, ts++, s1test2.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"8," + TEST_ROOT_DN_STRING2, cn, tn+"uuid8");
+      s1test2.publish(delMsg);
+
+      cn = new ChangeNumber(time++, ts++, s2test.getServerId());
+      delMsg =
+        new DeleteMsg("uid="+tn+"9," + TEST_ROOT_DN_STRING, cn, tn+"uuid9");
+      s2test.publish(delMsg);
+      sleep(500);
+
+      ReplicationServerDomain rsd =
+        replicationServer.getReplicationServerDomain(TEST_ROOT_DN_STRING, false);
+      ServerState startState = rsd.getStartState();
+      assertTrue(startState.getMaxChangeNumber(s1test.getServerId()).getSeqnum()==1);
+      assertTrue(startState.getMaxChangeNumber(s2test.getServerId()).getSeqnum()==7);
+
+      rsd =
+        replicationServer.getReplicationServerDomain(TEST_ROOT_DN_STRING2, false);
+      startState = rsd.getStartState();
+      assertTrue(startState.getMaxChangeNumber(s2test2.getServerId()).getSeqnum()==2);
+      assertTrue(startState.getMaxChangeNumber(s1test2.getServerId()).getSeqnum()==6);
+      
+      s1test.stop();
+      s1test2.stop();
+      s2test.stop();
+      s2test2.stop();
+      
+      removeTestBackend2(backend2);
+    }
+    catch(Exception e)
+    {
+      debugInfo(tn, "Ending test ECLDirectMsg with exception:\n"
+          +  stackTraceToSingleLineString(e));      
+      assertTrue(e == null);
+    }
+    debugInfo(tn, "Ending test successfully");
+  }
+
+  // simple update to be received
+  private void ECLDirectAllOps()
+  {
+    String tn = "ECLDirectAllOps";
+    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-1111-1111-1111-111111111111";
+      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, tn+"uuid1");
+      server01.publish(delMsg);
+      debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+
+      // Publish ADD
+      ChangeNumber cn2 = 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(
+          cn2, 
+          "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, tn+"uuid3");
+      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, tn+"uuid4",
+      "newparentId"));
+      LocalBackendModifyDNOperation localOp = new LocalBackendModifyDNOperation(op);
+      ModifyDNMsg modDNMsg = new ModifyDNMsg(localOp);
+      server01.publish(modDNMsg);
+      debugInfo(tn, " publishes " + modDNMsg.getChangeNumber());
+      sleep(600);
+
+      String cookie= "";
+
+      // search on 'cn=changelog'
+      debugInfo(tn, "STEP 1 - from empty cookie("+cookie+")");
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      attributes.add("+");
+      attributes.add("*");
+
+      ExternalChangelogRequestControl control =
+        new ExternalChangelogRequestControl(true, 
+            new MultiDomainServerState());
+      ArrayList<Control> controls = new ArrayList<Control>(0);
+      controls.add(control);
+
+      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);
+      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
+            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,"cookie","o=test:"+cn1.toString()+";");
+            checkValue(resultEntry,"targetentryuuid",tn+"uuid1");
+          } else if (i==2)
+          {
+            // check the ADD entry has the right content
+            String expectedValue1 = "objectClass: domain\nobjectClass: top\n" +
+            "entryUUID: 11111111-1111-1111-1111-111111111111\n\n";
+            String expectedValue2 = "entryUUID: 11111111-1111-1111-1111-111111111111\n" +
+            "objectClass: domain\nobjectClass: top\n\n";
+            checkPossibleValues(resultEntry,"changes",expectedValue1, expectedValue2);
+            checkValue(resultEntry,"replicationcsn",cn2.toString());
+            checkValue(resultEntry,"replicaidentifier","1201");
+            checkValue(resultEntry,"targetdn","uid="+tn+"2," + TEST_ROOT_DN_STRING);
+            checkValue(resultEntry,"changetype","add");
+            checkValue(resultEntry,"cookie","o=test:"+cn2.toString()+";");
+            checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+
+          } else if (i==3)
+          {
+            // check the MOD entry has the right content
+            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,"cookie","o=test:"+cn3.toString()+";");
+            checkValue(resultEntry,"targetentryuuid",tn+"uuid3");
+          } else if (i==4)
+          {
+            // check the MODDN entry has the right content
+            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,"cookie","o=test:"+cn4.toString()+";");
+            checkValue(resultEntry,"targetentryuuid",tn+"uuid4");
+            checkValue(resultEntry,"newrdn","uid=ECLDirectAllOpsnew4");            
+            checkValue(resultEntry,"newsuperior",TEST_ROOT_DN_STRING2);
+            checkValue(resultEntry,"deleteoldrdn","true");
+          }
+        }
+      }
+      server01.stop();
+    }
+    catch(Exception e)
+    {
+      debugInfo(tn, "Ending test with exception:\n"
+          +  stackTraceToSingleLineString(e));      
+      assertTrue(e == null);
+    }
+    debugInfo(tn, "Ending test with success");
+  }
+
+  private static void checkValue(Entry entry, String attrName, String expectedValue)
+  {
+    AttributeValue av = null;
+    try
+    {
+    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); 
+    }
+    catch(Exception e)
+    {
+      assertTrue(false,
+          "In entry " + entry + " attr <" + attrName + "> equals " +
+          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();
+      assertTrue(
+          (encodedValue.equalsIgnoreCase(expectedValue1) ||
+              encodedValue.equalsIgnoreCase(expectedValue2)),
+        "In entry " + entry + " attr <" + attrName + "> equals " +
+        av + " instead of one of the expected values " + expectedValue1
+        + " or " + expectedValue2); 
+    }
+    catch(Exception e)
+    {
+      assertTrue(false,
+          "In entry " + entry + " attr <" + attrName + "> equals " +
+          av + " instead of one of the expected values " + expectedValue1
+          + " or " + expectedValue2); 
+    }
+  }
+  
+  /**
+   * Test persistent search
+   */
+  private void ECLDirectPsearch(boolean changesOnly)
+  {
+    String tn = "ECLDirectPsearch_" + String.valueOf(changesOnly);
+    debugInfo(tn, "Starting test \n\n");
+    Socket s =null;
+
+    // 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 suffix
+      ReplicationBroker server01 = openReplicationSession(
+          DN.decode(TEST_ROOT_DN_STRING), (short) 1201, 
+          100, replicationServerPort,
+          1000, true);
+      int ts = 1;
+
+      // 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");
+      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("");
+
+      // 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);
+
+      // Creates request
+      SearchRequestProtocolOp searchRequest =
+        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=test)"),
+            null);
+
+      // Connects and bind
+      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);
+      s.setSoTimeout(1500000);
+      bindAsManager(w, r);
+
+      // 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();
+
+      debugInfo(tn, "Sending the PSearch request filter=(targetDN=*"+tn+"*,o=test)");
+      LDAPMessage message;
+      message = new LDAPMessage(2, searchRequest, controls);
+      w.writeMessage(message);
+      this.sleep(500);
+
+      SearchResultEntryProtocolOp searchResultEntry = null;
+      SearchResultDoneProtocolOp searchResultDone = null;
+
+      if (changesOnly == false)
+      {
+        // Wait for 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 " + 
+                message.getProtocolOpType() + message + " " + searchEntries);
+            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;
+            }
+          }
+        }
+        catch(Exception e)
+        {
+          debugInfo(tn, "INIT search failed with e=" +
+              stackTraceToSingleLineString(e));        
+        }
+        debugInfo(tn, "INIT search done with success. searchEntries="
+            + 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");
+      debugInfo(tn, " publishing " + delMsg.getChangeNumber());
+      server01.publish(delMsg);
+      this.sleep(1000);
+
+      debugInfo(tn, delMsg.getChangeNumber() +
+          " published , will wait for new entries (Persist)");
+
+      // wait for the 1 new entry
+      searchEntries = 0;
+      searchResultEntry = null;
+      searchResultDone = null;
+      message = null;
+      while ((searchEntries<1) && (message = r.readMessage()) != null)
+      {
+        debugInfo(tn, "2nd search returns " + 
+            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 2
+      for (LDAPAttribute a : searchResultEntry.getAttributes())
+      {
+        if (a.getAttributeType().equalsIgnoreCase("targetDN"))
+        {
+          for (ByteString av : a.getValues())
+          {
+            assertTrue(av.toString().equalsIgnoreCase(expectedDn),
+                "Entry returned by psearch is " + av.toString() +
+                " when expected is " + expectedDn);
+          }
+        }
+      }
+      debugInfo(tn, "Second search done successfully : " + searchResultEntry);
+      server01.stop();
+      try { s.close(); } catch (Exception e) {};
+      sleep(1000);
+
+      // TODO:  Testing ACI is disabled because it is currently failing when
+      // ran in the precommit target while it works well when running alone.
+      // anonymous search returns entries from cn=changelog whereas it
+      // should not. Probably a previous test in the nightlytests suite is
+      // removing/modifying some ACIs...
+      // 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 =
+        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, getControls(""));
+      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)
+    {
+      assertTrue(e==null, stackTraceToSingleLineString(e));
+    }
+    debugInfo(tn, "Ends test successfuly");
+  }
+
+  // utility - bind as required
+  private void bindAsManager(LDAPWriter w, org.opends.server.tools.LDAPReader r)
+  throws IOException, LDAPException, ASN1Exception, InterruptedException
+  {
+    bindAsWhoEver(w, r, 
+        "cn=Directory Manager", "password", LDAPResultCode.SUCCESS);
+  }
+
+  // utility - bind as required
+  private void bindAsWhoEver(LDAPWriter w, org.opends.server.tools.LDAPReader r,
+      String bindDN, String password,  int expected)
+  throws IOException, LDAPException, ASN1Exception, InterruptedException
+  {
+//  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();
+    BindRequestProtocolOp bindRequest =
+      new BindRequestProtocolOp(
+          ByteString.valueOf(bindDN),
+          3, ByteString.valueOf(password));
+    LDAPMessage message = new LDAPMessage(1, bindRequest);
+    w.writeMessage(message);
+
+    message = r.readMessage();
+    BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
+//  assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
+    assertEquals(bindResponse.getResultCode(), expected);
+  }
+
+  /**
+   * Clean up the environment.
+   *
+   * @throws Exception If the environment could not be set up.
+   */
+  @AfterClass
+
+  public void classCleanUp() throws Exception
+  {
+    callParanoiaCheck = false;
+    super.classCleanUp();
+
+    shutdown();
+
+    paranoiaCheck();
+  }
+
+  /**
+   * After the tests stop the replicationServer.
+   */
+  protected void shutdown() throws Exception
+  {
+    if (replicationServer != null)
+      replicationServer.remove();
+    /*
+    TestCaseUtils.dsconfig(
+        "delete-replication-server",
+        "--provider-name", "Multimaster Synchronization");
+        */
+    replicationServer = null;
+  }
+  /**
+   * Utility - sleeping as long as required
+   */
+  private void sleep(long time)
+  {
+    try
+    {
+      Thread.sleep(time);
+    } catch (InterruptedException ex)
+    {
+      fail("Error sleeping " + ex.getMessage());
+    }
+  }
+
+  /**
+   * Utility - log debug message - highlight it is from the test and not 
+   * from the server code. Makes easier to observe the test steps.
+   */
+  private void debugInfo(String tn, String s)
+  {
+    //if (debugEnabled())
+    {
+      logError(Message.raw(Category.SYNC, Severity.NOTICE,
+          "** TEST " + tn + " ** " + s));
+      TRACER.debugInfo("** TEST " + tn + " ** " + s);
+    }
+  }
+
+  /**
+   * Utility - create a second backend in order to test ECL with 2 suffixes.
+   */
+  private static Backend initializeTestBackend2(boolean createBaseEntry)
+  throws IOException, InitializationException, ConfigException,
+         DirectoryException
+  {
+
+    DN baseDN = DN.decode(TEST_ROOT_DN_STRING2);
+
+    //  Retrieve backend. Warning: it is important to perform this each time,
+    //  because a test may have disabled then enabled the backend (i.e a test
+    //  performing an import task). As it is a memory backend, when the backend
+    //  is re-enabled, a new backend object is in fact created and old reference
+    //  to memory backend must be invalidated. So to prevent this problem, we
+    //  retrieve the memory backend reference each time before cleaning it.
+    MemoryBackend memoryBackend =
+      (MemoryBackend)DirectoryServer.getBackend(TEST_BACKEND_ID2);
+
+    if (memoryBackend == null)
+    {
+      memoryBackend = new MemoryBackend();
+      memoryBackend.setBackendID(TEST_BACKEND_ID2);
+      memoryBackend.setBaseDNs(new DN[] {baseDN});
+      memoryBackend.initializeBackend();
+      DirectoryServer.registerBackend(memoryBackend);
+    }
+
+    memoryBackend.clearMemoryBackend();
+
+    if (createBaseEntry)
+    {
+      Entry e = createEntry(baseDN);
+      memoryBackend.addEntry(e, null);
+    }
+    return memoryBackend;
+  }
+  
+  private static void removeTestBackend2(Backend backend)
+  {
+    MemoryBackend memoryBackend = (MemoryBackend)backend;
+    memoryBackend.finalizeBackend();
+    DirectoryServer.deregisterBackend(memoryBackend);
+  }
+}
\ No newline at end of file
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 00afcce..4a2b53c 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
@@ -22,20 +22,26 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Copyright 2006-2009 Sun Microsystems, Inc.
  */
 package org.opends.server.replication.protocol;
 
-import static org.opends.server.replication.protocol.OperationContext.*;
-import static org.testng.Assert.*;
+import static org.opends.server.TestCaseUtils.TEST_ROOT_DN_STRING;
+import static org.opends.server.replication.protocol.OperationContext.SYNCHROCONTEXT;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
-import java.util.Iterator;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 import java.util.zip.DataFormatException;
 
+import org.opends.messages.Message;
 import org.opends.server.core.AddOperation;
 import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.DeleteOperationBasis;
@@ -44,8 +50,13 @@
 import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.replication.ReplicationTestCase;
+import org.opends.server.replication.common.AssuredMode;
 import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.DSInfo;
+import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.replication.common.RSInfo;
 import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.common.ServerStatus;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeBuilder;
 import org.opends.server.types.AttributeType;
@@ -60,15 +71,9 @@
 import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation;
 import org.opends.server.workflowelement.localbackend.LocalBackendDeleteOperation;
 import org.opends.server.workflowelement.localbackend.LocalBackendModifyDNOperation;
-import org.opends.messages.Message;
-import org.opends.server.replication.common.AssuredMode;
-import org.opends.server.replication.common.DSInfo;
-import org.opends.server.replication.common.RSInfo;
-import org.opends.server.replication.common.ServerStatus;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
-import static org.opends.server.TestCaseUtils.*;
 
 /**
  * Test the constructors, encoders and decoders of the replication protocol
@@ -563,18 +568,18 @@
          throws Exception
   {
     AckMsg msg1, msg2 ;
-
+  
     // Consctructor test (with ChangeNumber)
     // Chech that retrieved CN is OK
     msg1 = new  AckMsg(cn);
     assertEquals(msg1.getChangeNumber().compareTo(cn), 0);
-
+  
     // Check default values for error info
     assertFalse(msg1.hasTimeout());
     assertFalse(msg1.hasWrongStatus());
     assertFalse(msg1.hasReplayError());
     assertTrue(msg1.getFailedServers().size() == 0);
-
+  
     // Check constructor with error info
     msg1 = new  AckMsg(cn, hasTimeout, hasWrongStatus, hasReplayError, failedServers);
     assertEquals(msg1.getChangeNumber().compareTo(cn), 0);
@@ -582,7 +587,7 @@
     assertTrue(msg1.hasWrongStatus() == hasWrongStatus);
     assertTrue(msg1.hasReplayError() == hasReplayError);
     assertEquals(msg1.getFailedServers(), failedServers);
-
+  
     // Consctructor test (with byte[])
     msg2 = new  AckMsg(msg1.getBytes());
     assertEquals(msg2.getChangeNumber().compareTo(cn), 0);
@@ -590,7 +595,7 @@
     assertTrue(msg1.hasWrongStatus() == msg2.hasWrongStatus());
     assertTrue(msg1.hasReplayError() == msg2.hasReplayError());
     assertEquals(msg1.getFailedServers(), msg2.getFailedServers());
-
+  
     // Check invalid bytes for constructor
     byte[] b = msg1.getBytes();
     b[0] = ReplicationMsg.MSG_TYPE_ADD;
@@ -604,11 +609,53 @@
     {
       assertTrue(true);
     }
-
+  
     // Check that retrieved CN is OK
     msg2 = (AckMsg) ReplicationMsg.generateMsg(msg1.getBytes());
   }
 
+  @Test()
+  public void eclUpdateMsg()
+         throws Exception
+  {
+    // create a msg to put in the eclupdatemsg
+    InternalClientConnection connection =
+      InternalClientConnection.getRootConnection();
+    DeleteOperationBasis opBasis =
+      new DeleteOperationBasis(connection, 1, 1,null, DN.decode("cn=t1"));
+    LocalBackendDeleteOperation op = new LocalBackendDeleteOperation(opBasis);
+    ChangeNumber cn = new ChangeNumber(TimeThread.getTime(),
+        (short) 123, (short) 45);
+    op.setAttachment(SYNCHROCONTEXT, new DeleteContext(cn, "uniqueid"));
+    DeleteMsg delmsg = new DeleteMsg(op);
+
+    String serviceId = "serviceid";
+
+    // create a cookie
+    MultiDomainServerState cookie = 
+      new MultiDomainServerState(
+          "o=test:000001210b6f21e904b100000001 000001210b6f21e904b200000001;" +
+          "o=test2:000001210b6f21e904b100000002 000001210b6f21e904b200000002;");
+    
+    // Constructor test
+    ECLUpdateMsg msg1 = new ECLUpdateMsg(delmsg, cookie, serviceId);
+    assertTrue(msg1.getCookie().equalsTo(cookie));
+    assertTrue(msg1.getServiceId().equalsIgnoreCase(serviceId));
+    DeleteMsg delmsg2 = (DeleteMsg)msg1.getUpdateMsg();
+    assertTrue(delmsg.compareTo(delmsg2)==0);
+
+    // Consctructor test (with byte[])
+    ECLUpdateMsg msg2 = new ECLUpdateMsg(msg1.getBytes());
+    assertTrue(msg2.getCookie().equalsTo(msg2.getCookie()));
+    assertTrue(msg2.getCookie().equalsTo(cookie));
+    assertTrue(msg2.getServiceId().equalsIgnoreCase(msg1.getServiceId()));
+    assertTrue(msg2.getServiceId().equalsIgnoreCase(serviceId));
+    DeleteMsg delmsg1 = (DeleteMsg)msg1.getUpdateMsg();
+    delmsg2 = (DeleteMsg)msg2.getUpdateMsg();
+    assertTrue(delmsg2.compareTo(delmsg)==0);
+    assertTrue(delmsg2.compareTo(delmsg1)==0);
+  }
+
   @DataProvider(name="createServerStartData")
   public Object [][] createServerStartData() throws Exception
   {
@@ -1120,4 +1167,85 @@
     UpdateMsg newMsg = new UpdateMsg(msg.getBytes());
     assertEquals(test.getBytes(), newMsg.getPayload());
   }
+  
+  /**
+   * Test that ServerStartMsg encoding and decoding works
+   * by checking that : msg == new ServerStartMsg(msg.getBytes()).
+   */
+  @Test(dataProvider="createServerStartData")
+  public void startECLMsgTest(short serverId, String baseDN, int window,
+         ServerState state, long genId, boolean sslEncryption, byte groupId) throws Exception
+  {
+    ServerStartECLMsg msg = new ServerStartECLMsg(baseDN,
+        window, window, window, window, window, window, state,
+        ProtocolVersion.getCurrentVersion(), genId, sslEncryption, groupId);
+    ServerStartECLMsg newMsg = new ServerStartECLMsg(msg.getBytes());
+    assertEquals(msg.getMaxReceiveDelay(), newMsg.getMaxReceiveDelay());
+    assertEquals(msg.getMaxReceiveQueue(), newMsg.getMaxReceiveQueue());
+    assertEquals(msg.getMaxSendDelay(), newMsg.getMaxSendDelay());
+    assertEquals(msg.getMaxSendQueue(), newMsg.getMaxSendQueue());
+    assertEquals(msg.getWindowSize(), newMsg.getWindowSize());
+    assertEquals(msg.getHeartbeatInterval(), newMsg.getHeartbeatInterval());
+    assertEquals(msg.getSSLEncryption(), newMsg.getSSLEncryption());
+    assertEquals(msg.getServerState().getMaxChangeNumber((short)1),
+        newMsg.getServerState().getMaxChangeNumber((short)1));
+    assertEquals(msg.getVersion(), newMsg.getVersion());
+    assertEquals(msg.getGenerationId(), newMsg.getGenerationId());
+    assertTrue(msg.getGroupId() == newMsg.getGroupId());
+  }
+  /**
+   * Test StartSessionMsg encoding and decoding.
+   */
+  @Test()
+  public void startECLSessionMsgTest()
+    throws Exception
+  {
+    // data
+    ChangeNumber changeNumber = new ChangeNumber(TimeThread.getTime(),
+        (short) 123, (short) 45);
+    String generalizedState = new String("fakegenstate");
+    ServerState state = new ServerState();
+    assertTrue(state.update(new ChangeNumber((long)75, 5,(short)263)));
+    short mode = 3;
+    int firstDraftChangeNumber = 13;    
+    int lastDraftChangeNumber  = 14;  
+    String myopid = new String("fakeopid");
+    // create original
+    StartECLSessionMsg msg = new StartECLSessionMsg();
+    msg.setChangeNumber(changeNumber);
+    msg.setCrossDomainServerState(generalizedState);
+    msg.setPersistent(StartECLSessionMsg.PERSISTENT);
+    msg.setFirstDraftChangeNumber(firstDraftChangeNumber);
+    msg.setLastDraftChangeNumber(lastDraftChangeNumber);
+    msg.setECLRequestType(mode);
+    msg.setOperationId(myopid);
+    ArrayList<String> dns = new ArrayList<String>();
+    String dn1 = "cn=admin data";
+    String dn2 = "cn=config";
+    dns.add(dn1);
+    dns.add(dn2);
+    msg.setExcludedDNs(dns);
+    // create copy
+    StartECLSessionMsg newMsg = new StartECLSessionMsg(msg.getBytes());
+    // test equality between the two copies
+    assertEquals(msg.getChangeNumber(), newMsg.getChangeNumber());
+    assertTrue(msg.isPersistent() == newMsg.isPersistent());
+    assertTrue(msg.getFirstDraftChangeNumber() == newMsg.getFirstDraftChangeNumber());
+    assertEquals(msg.getECLRequestType(), newMsg.getECLRequestType());
+    assertEquals(msg.getLastDraftChangeNumber(), newMsg.getLastDraftChangeNumber());
+    assertTrue(
+        msg.getCrossDomainServerState().equalsIgnoreCase(newMsg.getCrossDomainServerState()));
+    assertTrue(
+        msg.getOperationId().equalsIgnoreCase(newMsg.getOperationId()));
+    ArrayList<String> dns2 = newMsg.getExcludedServiceIDs();
+    assertTrue(dns2.size()==2);
+    boolean dn1found=false,dn2found=false;
+    for (String dn : dns2)
+    {
+      if (!dn1found) dn1found=(dn.compareTo(dn1)==0);
+      if (!dn2found) dn2found=(dn.compareTo(dn2)==0);
+    }
+    assertTrue(dn1found);
+    assertTrue(dn2found);
+  }
 }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java
index 89bce13..eeb893f 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java
@@ -90,6 +90,8 @@
 import org.opends.server.util.LDIFWriter;
 import org.opends.server.util.TimeThread;
 import static org.opends.server.util.ServerConstants.OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
 import org.opends.server.workflowelement.localbackend.LocalBackendModifyDNOperation;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
@@ -175,7 +177,7 @@
 
   private void debugInfo(String s)
   {
-    ErrorLogger.logError(Message.raw(Category.SYNC, Severity.NOTICE, "** TEST ** " + s));
+    //ErrorLogger.logError(Message.raw(Category.SYNC, Severity.NOTICE, "** TEST ** " + s));
     if (debugEnabled())
     {
       TRACER.debugInfo("** TEST ** " + s);
@@ -689,7 +691,7 @@
           new ChangeNumberGenerator(serverId , (long) 0);
         broker[i] =
           openReplicationSession( DN.decode(TEST_ROOT_DN_STRING), serverId,
-            100, replicationServerPort, 100000, 1000, 0, false);
+              100, replicationServerPort, 3000, 1000, 0, true);
 
         assertTrue(broker[i].isConnected());
 
@@ -706,10 +708,11 @@
       {
         reader[i].start();
       }
-      debugInfo("Ending multipleWriterMultipleReader");
+      debugInfo("multipleWriterMultipleReader produces and readers started");
     }
     finally
     {
+      debugInfo("multipleWriterMultipleReader wait producers");
       for (int i = 0; i< THREADS; i++)
       {
         if (producer[i] != null)
@@ -717,6 +720,7 @@
         // kill the thread in case it is not yet stopped.
         producer[i].interrupt();
       }
+      debugInfo("multipleWriterMultipleReader producers done, wait readers");
       for (int i = 0; i< THREADS; i++)
       {
         if (reader[i] != null)
@@ -724,15 +728,18 @@
         // kill the thread in case it is not yet stopped.
         reader[i].interrupt();
       }
+      debugInfo("multipleWriterMultipleReader reader's done");
       for (int i = 0; i< THREADS; i++)
       {
         if (broker[i] != null)
           broker[i].stop();
       }
+      debugInfo("multipleWriterMultipleReader brokers stopped");
 
       replicationServer.clearDb();
       TestCaseUtils.initializeTestBackend(true);
     }
+    debugInfo("Ending multipleWriterMultipleReader");
   }
 
 
@@ -1123,6 +1130,8 @@
    */
   private class BrokerReader extends Thread
   {
+    int count;
+
     private ReplicationBroker broker;
     private int numMsgRcv = 0;
     private final int numMsgExpected;
@@ -1142,8 +1151,11 @@
     @Override
     public void run()
     {
+      debugInfo("BrokerReader " + broker.getServerId() + " starts");
+
       // loop receiving messages until either we get a timeout
       // because there is nothing left or an error condition happens.
+      count = 0;
       try
       {
         while (true)
@@ -1165,7 +1177,8 @@
       } catch (Exception e)
       {
         assertTrue(false,
-            "a BrokerReader received an Exception" + e.getMessage());
+            "a BrokerReader received an Exception" + e.getMessage()
+            + stackTraceToSingleLineString(e));
       }
     }
   }
@@ -1194,6 +1207,8 @@
     @Override
     public void run()
     {
+      debugInfo("BrokerWriter " + broker.getServerId() + " starts");
+      int ccount = count;
       /*
        * Simple loop creating changes and sending them
        * to the replicationServer.
@@ -1206,7 +1221,11 @@
           new DeleteMsg("o=example," + TEST_ROOT_DN_STRING, gen.newChangeNumber(),
               "uid");
         broker.publish(msg);
+        
+        if ((count % 10) == 0)
+        debugInfo("BrokerWriter " + broker.getServerId() + "  sent="+count);
       }
+      debugInfo("BrokerWriter " + broker.getServerId() + " ends sent="+ccount);
     }
   }
 

--
Gitblit v1.10.0