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

gbellato
08.03.2008 d04fb0f282e0fd9a4bc80d3f9d5ee15506a3b83b
Merge the replication-service branch with the OpenDS trunk

This commit includes all the changes for :

- issue 3640 : Refactor Replication code to make it more generic
- issue 497 : Assured replication

3 files deleted
12 files added
9 files renamed
67 files modified
13043 ■■■■■ changed files
opends/resource/config/config.ldif 6 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/replication.properties 19 ●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/installer/Installer.java 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/common/ServerState.java 31 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/common/ServerStatus.java 27 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/FakeAddOperation.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/FakeModdnOperation.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java 1874 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java 59 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/PendingChange.java 10 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/PendingChanges.java 48 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java 72 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/RemotePendingChanges.java 40 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/ReplLDIFOutputStream.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/ReplayThread.java 7 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/ReplicationMonitor.java 213 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/UpdateToReplay.java 14 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/AckMsg.java 67 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/AddMsg.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/EntryMsg.java 29 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/InitializeRequestMsg.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/InitializeTargetMsg.java 19 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/LDAPUpdateMsg.java 492 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ModifyCommonMsg.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java 20 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ReplSessionSecurity.java 24 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java 7 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java 16 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/UpdateMsg.java 596 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/WindowMsg.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/AckMessageList.java 99 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/DbHandler.java 5 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ExpectedAcksInfo.java 141 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/MonitorData.java 66 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/NotAssuredUpdateMsg.java 358 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplServerAckMessageList.java 84 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplicationBackend.java 280 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplicationDB.java 5 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplicationDbEnv.java 53 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplicationServer.java 20 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java 672 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java 110 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/SafeReadExpectedAcksInfo.java 240 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ServerHandler.java 313 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ServerReader.java 8 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ServerWriter.java 6 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/InitializeTargetTask.java 45 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/InitializeTask.java 37 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/ListenerThread.java 38 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/ReplInputStream.java 10 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/ReplOutputStream.java 82 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/ReplicationBroker.java 450 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/ReplicationDomain.java 2433 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/ReplicationMonitor.java 331 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/SetGenerationIdTask.java 32 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/service/package-info.java 26 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliMain.java 4 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ChangeNumberControlPluginTestCase.java 44 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/DependencyTest.java 18 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/GenerationIdTest.java 325 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/InitOnLineTest.java 141 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ProtocolWindowTest.java 3 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReSyncTest.java 13 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java 45 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java 10 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/StressTest.java 4 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java 80 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java 1535 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ComputeBestServerTest.java 52 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/GroupIdHandshakeTest.java 24 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalCsnOrderingTest.java 11 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalTest.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/IsolationTest.java 11 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java 7 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentServerStateTest.java 12 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ReplicationServerFailoverTest.java 12 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/StateMachineTest.java 159 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/TopologyViewTest.java 56 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/ProtocolCompatibilityTest.java 20 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java 68 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/DbHandlerTest.java 5 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/MonitorTest.java 9 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerDynamicConfTest.java 4 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java 66 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeReplicationDomain.java 149 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeStressReplicationDomain.java 159 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/ReplicationDomainTest.java 317 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -58,9 +58,9 @@
ds-cfg-allowed-task: org.opends.server.tasks.EnterLockdownModeTask
ds-cfg-allowed-task: org.opends.server.tasks.ExportTask
ds-cfg-allowed-task: org.opends.server.tasks.ImportTask
ds-cfg-allowed-task: org.opends.server.tasks.InitializeTargetTask
ds-cfg-allowed-task: org.opends.server.tasks.InitializeTask
ds-cfg-allowed-task: org.opends.server.tasks.SetGenerationIdTask
ds-cfg-allowed-task: org.opends.server.replication.service.InitializeTargetTask
ds-cfg-allowed-task: org.opends.server.replication.service.InitializeTask
ds-cfg-allowed-task: org.opends.server.replication.service.SetGenerationIdTask
ds-cfg-allowed-task: org.opends.server.tasks.LeaveLockdownModeTask
ds-cfg-allowed-task: org.opends.server.tasks.RebuildTask
ds-cfg-allowed-task: org.opends.server.tasks.RestoreTask
opends/src/messages/messages/replication.properties
@@ -109,8 +109,9 @@
 connection
SEVERE_ERR_WRITER_UNEXPECTED_EXCEPTION_32=An unexpected error happened \
 handling connection with %s.  This connection is going to be closed
SEVERE_ERR_CHANGELOG_ERROR_SENDING_ACK_33=An unexpected error occurred  while \
 sending an ack to %s.  This connection is going to be closed and reopened
SEVERE_ERR_RS_ERROR_SENDING_ACK_33=In replication server %s: an unexpected error \
 occurred while sending an ack to server id %s for change number %s in domain %s \
 . This connection is going to be closed and reopened
SEVERE_ERR_EXCEPTION_RECEIVING_REPLICATION_MESSAGE_34=An Exception was caught \
 while receiving replication message : %s
MILD_ERR_LOOP_REPLAYING_OPERATION_35=A loop was detected while replaying \
@@ -269,7 +270,7 @@
DEBUG_SENDING_CHANGE_112=Sending change number: %s
DEBUG_CHANGES_SENT_113=All missing changes sent to replication server
SEVERE_ERR_PUBLISHING_FAKE_OPS_114=Caught exception publishing fake operations \
for domain %s to replication server %s : %s
for domain %s : %s
SEVERE_ERR_COMPUTING_FAKE_OPS_115=Caught exception computing fake operations \
for domain %s for replication server %s : %s
NOTICE_IGNORING_REMOTE_MONITOR_DATA_116=Some monitor data have been received \
@@ -346,3 +347,15 @@
server %s will be aborted. Simultanate cross connection attempt ?
NOTICE_BAD_GENERATION_ID_FROM_DS_146=On suffix %s, directory server %s presented \
 generation ID=%s when expected generation ID=%s
SEVERE_ERR_DS_RECEIVED_ACK_ERROR_147=In replication service %s, the assured \
 update message %s was acknowledged with the following errors: %s
SEVERE_ERR_DS_ACK_TIMEOUT_148=In replication service %s, timeout after %s ms \
 waiting for the acknowledgement of the assured update message: %s
SEVERE_ERR_DS_UNKNOWN_ASSURED_MODE_149=In directory server %s, received unknown \
 assured update mode: %s, for domain %s. Message: %s
SEVERE_ERR_RS_UNKNOWN_ASSURED_MODE_150=In replication server %s, received unknown \
 assured update mode: %s, for domain %s. Message: %s
SEVERE_ERR_UNKNOWN_ASSURED_SAFE_DATA_LEVEL_151=In replication server %s, \
 received a safe data assured update message with incoherent level: %s, this is \
 for domain %s. Message: %s
opends/src/quicksetup/org/opends/quicksetup/installer/Installer.java
@@ -4204,7 +4204,9 @@
    oc.add("ds-task");
    oc.add("ds-task-initialize-from-remote-replica");
    attrs.put(oc);
    attrs.put("ds-task-class-name", "org.opends.server.tasks.InitializeTask");
    attrs.put(
        "ds-task-class-name",
        "org.opends.server.replication.service.InitializeTask");
    attrs.put("ds-task-initialize-domain-dn", suffixDn);
    attrs.put("ds-task-initialize-replica-server-id",
        String.valueOf(replicaId));
@@ -4521,7 +4523,7 @@
    oc.add("ds-task-reset-generation-id");
    attrs.put(oc);
    attrs.put("ds-task-class-name",
        "org.opends.server.tasks.SetGenerationIdTask");
        "org.opends.server.replication.service.SetGenerationIdTask");
    attrs.put("ds-task-reset-generation-id-domain-base-dn", suffixDn);
    while (!taskCreated)
    {
opends/src/server/org/opends/server/replication/common/ServerState.java
@@ -49,6 +49,7 @@
public class ServerState implements Iterable<Short>
{
  private HashMap<Short, ChangeNumber> list;
  private boolean saved = true;
  /**
   * Creates a new empty ServerState.
@@ -142,16 +143,18 @@
  /**
   * Update the Server State with a ChangeNumber.
   * All operations with smaller CSN and the same serverID must be committed
   * before calling this method.
   * @param changeNumber the committed ChangeNumber.
   * @return a boolean indicating if the update was meaningfull.
   *
   * @param changeNumber    The committed ChangeNumber.
   *
   * @return a boolean indicating if the update was meaningful.
   */
  public boolean update(ChangeNumber changeNumber)
  {
    if (changeNumber == null)
      return false;
    saved = false;
    synchronized(this)
    {
      Short id =  changeNumber.getServerId();
@@ -395,4 +398,24 @@
     return diff;
  }
  /**
   * Set the saved status of this ServerState.
   *
   * @param b A booelan indicating if the State has been safely stored.
   */
  public void setSaved(boolean b)
  {
    saved = false;
  }
  /**
   * Get the saved status of this ServerState.
   *
   * @return The saved status of this ServerState.
   */
  public boolean isSaved()
  {
    return saved;
  }
}
opends/src/server/org/opends/server/replication/common/ServerStatus.java
@@ -131,4 +131,31 @@
  {
    return value;
  }
  /**
   * Get a user readable string representing this status (User friendly string
   * for monitoring purpose).
   * @return A user readable string representing this status.
   */
  public String toString()
  {
    switch (value)
    {
      case -1:
        return "Invalid";
      case 0:
        return "Not connected";
      case 1:
        return "Normal";
      case 2:
        return "Degraded";
      case 3:
        return "Full update";
      case 4:
        return "Bad generation id";
      default:
        throw new IllegalArgumentException("Wrong status numeric value: " +
          value);
    }
  }
}
opends/src/server/org/opends/server/replication/plugin/FakeAddOperation.java
@@ -62,7 +62,7 @@
  {
    return new AddMsg(getChangeNumber(), entry.getDN().toString(),
               Historical.getEntryUuid(entry),
               ReplicationDomain.findEntryId(
               LDAPReplicationDomain.findEntryId(
                   entry.getDN().getParentDNInSuffix()),
               entry.getObjectClasses(),
               entry.getUserAttributes(), entry.getOperationalAttributes());
opends/src/server/org/opends/server/replication/plugin/FakeModdnOperation.java
@@ -66,7 +66,7 @@
    DN dn = entry.getDN();
    return new ModifyDNMsg(dn.toString(), this.getChangeNumber(),
        Historical.getEntryUuid(entry),
        ReplicationDomain.findEntryId(dn.getParent()),
        LDAPReplicationDomain.findEntryId(dn.getParent()),
        false, dn.getParent().toString(), dn.getRDN().toString());
  }
}
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
File was renamed from opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
@@ -25,6 +25,7 @@
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
import static org.opends.messages.ReplicationMessages.*;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.loggers.ErrorLogger.logError;
@@ -36,28 +37,31 @@
import static org.opends.server.util.StaticUtils.createEntry;
import static org.opends.server.util.StaticUtils.getFileForPath;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import static org.opends.server.replication.common.StatusMachine.*;
import org.opends.server.replication.protocol.LDAPUpdateMsg;
import org.opends.server.replication.service.ReplicationMonitor;
import java.util.Collection;
import org.opends.server.types.Attributes;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.CheckedOutputStream;
import java.util.zip.DataFormatException;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.admin.server.ConfigurationChangeListener;
@@ -68,7 +72,6 @@
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.backends.jeb.BackendImpl;
import org.opends.server.backends.task.Task;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DeleteOperation;
@@ -82,49 +85,31 @@
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchListener;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.replication.service.ReplicationDomain;
import org.opends.server.replication.common.AssuredMode;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
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.AckMsg;
import org.opends.server.replication.protocol.AddContext;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.ChangeStatusMsg;
import org.opends.server.replication.protocol.DeleteContext;
import org.opends.server.replication.protocol.DoneMsg;
import org.opends.server.replication.protocol.EntryMsg;
import org.opends.server.replication.protocol.ErrorMsg;
import org.opends.server.replication.protocol.HeartbeatMsg;
import org.opends.server.replication.protocol.InitializeRequestMsg;
import org.opends.server.replication.protocol.InitializeTargetMsg;
import org.opends.server.replication.protocol.ModifyContext;
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.OperationContext;
import org.opends.server.replication.protocol.ReplSessionSecurity;
import org.opends.server.replication.protocol.ReplicationMsg;
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.ProtocolSession;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.tasks.InitializeTargetTask;
import org.opends.server.tasks.InitializeTask;
import org.opends.server.tasks.TaskUtils;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.Attributes;
import org.opends.server.types.ExistingFileBehavior;
import org.opends.server.types.AbstractOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ConfigChangeResult;
@@ -133,6 +118,7 @@
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ExistingFileBehavior;
import org.opends.server.types.LDAPException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
@@ -144,6 +130,7 @@
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.types.SearchScope;
import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.types.operation.PluginOperation;
@@ -163,9 +150,9 @@
 *  handle conflict resolution,
 *  handle protocol messages from the replicationServer.
 */
public class ReplicationDomain extends DirectoryThread
public class LDAPReplicationDomain extends ReplicationDomain
       implements ConfigurationChangeListener<ReplicationDomainCfg>,
                  AlertGenerator
                  AlertGenerator, InternalSearchListener
{
  /**
   * The fully-qualified name of this class.
@@ -185,37 +172,20 @@
   */
  private static final DebugTracer TRACER = getTracer();
  private ReplicationMonitor monitor;
  private ReplicationBroker broker;
  // Thread waiting for incoming update messages for this domain and pushing
  // them to the global incoming update message queue for later processing by
  // replay threads.
  private ListenerThread listenerThread;
  // The update to replay message queue where the listener thread is going to
  // push incoming update messages.
  private LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue;
  private SortedMap<ChangeNumber, UpdateMsg> waitingAckMsgs =
    new TreeMap<ChangeNumber, UpdateMsg>();
  private AtomicInteger numRcvdUpdates = new AtomicInteger(0);
  private AtomicInteger numSentUpdates = new AtomicInteger(0);
  private AtomicInteger numProcessedUpdates = new AtomicInteger();
  private AtomicInteger numResolvedNamingConflicts = new AtomicInteger();
  private AtomicInteger numResolvedModifyConflicts = new AtomicInteger();
  private AtomicInteger numUnresolvedNamingConflicts = new AtomicInteger();
  private int debugCount = 0;
  private PersistentServerState state;
  private final PersistentServerState state;
  private int numReplayedPostOpCalled = 0;
  private int maxReceiveQueue = 0;
  private int maxSendQueue = 0;
  private int maxReceiveDelay = 0;
  private int maxSendDelay = 0;
  private long generationId = -1;
  private boolean generationIdSavedStatus = false;
  ChangeNumberGenerator generator;
  private ChangeNumberGenerator generator;
  /**
   * This object is used to store the list of update currently being
@@ -235,19 +205,8 @@
   */
  private RemotePendingChanges remotePendingChanges;
  /**
   * The time in milliseconds between heartbeats from the replication
   * server.  Zero means heartbeats are off.
   */
  private long heartbeatInterval = 0;
  private short serverId;
  // The context related to an import or export being processed
  // Null when none is being processed.
  private IEContext ieContext = null;
  private Collection<String> replicationServers;
  private DN baseDn;
  private boolean shutdown = false;
@@ -260,37 +219,10 @@
  private boolean disabled = false;
  private boolean stateSavingDisabled = false;
  private int window = 100;
  /*
   * Assured mode properties
   */
  // Is assured mode enabled or not for this domain ?
  private boolean assured = false;
  // Assured sub mode (used when assured is true)
  private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE;
  // Safe Data level (used when assuredMode is SAFE_DATA)
  private byte assuredSdLevel = (byte)1;
  // Timeout (in milliseconds) when waiting for acknowledgments
  private long assuredTimeout = 1000;
  // Group id
  private byte groupId = (byte)1;
  // Referrals urls to be published to other servers of the topology
  // TODO: fill that with all currently opened urls if no urls configured
  private List<String> refUrls = new ArrayList<String>();
  // Current status for this replicated domain
  private ServerStatus status = ServerStatus.NOT_CONNECTED_STATUS;
  /*
   * Properties for the last topology info received from the network.
   */
  // Info for other DSs.
  // Warning: does not contain info for us (for our server id)
  private List<DSInfo> dsList = new ArrayList<DSInfo>();
  // Info for other RSs.
  private List<RSInfo> rsList = new ArrayList<RSInfo>();
  // This list is used to temporary store operations that needs
  // to be replayed at session establishment time.
  private TreeSet<FakeOperation> replayOperations  =
    new TreeSet<FakeOperation>(new FakeOperationComparator());;
  /**
   * The isolation policy that this domain is going to use.
@@ -312,131 +244,47 @@
   */
  private boolean done = true;
  private ServerStateFlush flushThread;
  /**
   * This class contain the context related to an import or export
   * launched on the domain.
   * The thread that periodically saves the ServerState of this
   * LDAPReplicationDomain in the database.
   */
  private class IEContext
  private class  ServerStateFlush extends DirectoryThread
  {
    // The task that initiated the operation.
    Task initializeTask;
    // The input stream for the import
    ReplLDIFInputStream ldifImportInputStream = null;
    // The target in the case of an export
    short exportTarget = RoutableMsg.UNKNOWN_SERVER;
    // The source in the case of an import
    short importSource = RoutableMsg.UNKNOWN_SERVER;
    // The total entry count expected to be processed
    long entryCount = 0;
    // The count for the entry not yet processed
    long entryLeftCount = 0;
    // The exception raised when any
    DirectoryException exception = null;
    /**
     * Initializes the import/export counters with the provider value.
     * @param total
     * @param left
     * @throws DirectoryException
     */
    public void setCounters(long total, long left)
      throws DirectoryException
    protected ServerStateFlush()
    {
      entryCount = total;
      entryLeftCount = left;
      if (initializeTask != null)
      {
        if (initializeTask instanceof InitializeTask)
        {
          ((InitializeTask)initializeTask).setTotal(entryCount);
          ((InitializeTask)initializeTask).setLeft(entryCount);
        }
        else if (initializeTask instanceof InitializeTargetTask)
        {
          ((InitializeTargetTask)initializeTask).setTotal(entryCount);
          ((InitializeTargetTask)initializeTask).setLeft(entryCount);
        }
      }
    }
    /**
     * Update the counters of the task for each entry processed during
     * an import or export.
     * @throws DirectoryException
     */
    public void updateCounters()
      throws DirectoryException
    {
      entryLeftCount--;
      if (initializeTask != null)
      {
        if (initializeTask instanceof InitializeTask)
        {
          ((InitializeTask)initializeTask).setLeft(entryLeftCount);
        }
        else if (initializeTask instanceof InitializeTargetTask)
        {
          ((InitializeTargetTask)initializeTask).setLeft(entryLeftCount);
        }
      }
      super("Replication State Saver for server id " +
            serverId + " and domain " + baseDn.toString());
    }
    /**
     * {@inheritDoc}
     */
    public String toString()
    {
      return new String("[ Entry count=" + this.entryCount +
                        ", Entry left count=" + this.entryLeftCount + "]");
    }
  }
  /**
   * This thread is launched when we want to export data to another server that
   * has requested to be initialized with the data of our backend.
   */
  private class ExportThread extends DirectoryThread
  {
    // Id of server that will receive updates
    private short target;
    /**
     * Constructor for the ExportThread.
     *
     * @param target Id of server that will receive updates
     */
    public ExportThread(short target)
    {
      super("Export thread " + serverId);
      this.target = target;
    }
    /**
     * Run method for this class.
     */
    @Override
    public void run()
    {
      if (debugEnabled())
      {
        TRACER.debugInfo("Export thread starting.");
      }
      done = false;
      try
      while (shutdown  == false)
      {
        initializeRemote(target, target, null);
      } catch (DirectoryException de)
      {
      // An error message has been sent to the peer
      // Nothing more to do locally
        try
        {
          synchronized (this)
          {
            this.wait(1000);
            if (!disabled && !stateSavingDisabled )
            {
              // save the ServerState
              state.save();
            }
          }
        } catch (InterruptedException e)
        { }
      }
      if (debugEnabled())
      {
        TRACER.debugInfo("Export thread stopping.");
      }
      state.save();
      done = true;
    }
  }
@@ -447,18 +295,24 @@
   * @param updateToReplayQueue The queue for update messages to replay.
   * @throws ConfigException In case of invalid configuration.
   */
  public ReplicationDomain(ReplicationDomainCfg configuration,
  public LDAPReplicationDomain(ReplicationDomainCfg configuration,
    LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue)
    throws ConfigException
  {
    super("Replication State Saver for server id " + configuration.getServerId()
      + " and domain " + configuration.getBaseDN());
    super(configuration.getBaseDN().toNormalizedString(),
        (short) configuration.getServerId());
    /**
     * The time in milliseconds between heartbeats from the replication
     * server.  Zero means heartbeats are off.
     */
    long heartbeatInterval = 0;
    // Read the configuration parameters.
    replicationServers = configuration.getReplicationServer();
    Set<String> replicationServers = configuration.getReplicationServer();
    serverId = (short) configuration.getServerId();
    baseDn = configuration.getBaseDN();
    window  = configuration.getWindowSize();
    int window  = configuration.getWindowSize();
    heartbeatInterval = configuration.getHeartbeatInterval();
    isolationpolicy = configuration.getIsolationPolicy();
    configDn = configuration.dn();
@@ -471,28 +325,22 @@
    switch (assuredType)
    {
      case NOT_ASSURED:
        assured = false;
        setAssured(false);
        break;
      case SAFE_DATA:
        assured = true;
        this.assuredMode = AssuredMode.SAFE_DATA_MODE;
        setAssured(true);
        setAssuredMode(AssuredMode.SAFE_DATA_MODE);
        break;
      case SAFE_READ:
        assured = true;
        this.assuredMode = AssuredMode.SAFE_READ_MODE;
        setAssured(true);
        setAssuredMode(AssuredMode.SAFE_READ_MODE);
        break;
    }
    this.assuredSdLevel = (byte)configuration.getAssuredSdLevel();
    this.groupId = (byte)configuration.getGroupId();
    this.assuredTimeout = configuration.getAssuredTimeout();
    SortedSet<String> urls = configuration.getReferralsUrl();
    if (urls != null)
    {
      for (String url : urls)
      {
        this.refUrls.add(url);
      }
    }
    setAssuredSdLevel((byte)configuration.getAssuredSdLevel());
    setAssuredTimeout(configuration.getAssuredTimeout());
    setGroupId((byte)configuration.getGroupId());
    setURLs(configuration.getReferralsUrl());
    /*
     * Modify conflicts are solved for all suffixes but the schema suffix
@@ -510,19 +358,6 @@
      solveConflictFlag = true;
    }
    /*
     * Create a new Persistent Server State that will be used to store
     * the last ChangeNmber seen from all LDAP servers in the topology.
     */
    state = new PersistentServerState(baseDn, serverId);
    /*
     * Create a replication monitor object responsible for publishing
     * monitoring information below cn=monitor.
     */
    monitor = new ReplicationMonitor(this);
    DirectoryServer.registerMonitorProvider(monitor);
    Backend backend = retrievesBackend(baseDn);
    if (backend == null)
    {
@@ -541,14 +376,12 @@
    }
    /*
     * create the broker object used to publish and receive changes
     * Create a new Persistent Server State that will be used to store
     * the last ChangeNmber seen from all LDAP servers in the topology.
     */
    broker = new ReplicationBroker(this, state, baseDn, serverId,
        maxReceiveQueue, maxReceiveDelay, maxSendQueue, maxSendDelay, window,
        heartbeatInterval, generationId,
        new ReplSessionSecurity(configuration),getGroupId());
    state = new PersistentServerState(baseDn, serverId, getServerState());
    broker.start(replicationServers);
    startPublishService(replicationServers, window, heartbeatInterval);
    /*
     * ChangeNumberGenerator is used to create new unique ChangeNumbers
@@ -557,14 +390,12 @@
     * The generator time is adjusted to the time of the last CN received from
     * remote other servers.
     */
    generator =
      new ChangeNumberGenerator(serverId, state);
    generator = getGenerator();
    pendingChanges =
      new PendingChanges(generator,
                         broker, state);
      new PendingChanges(generator, this);
    remotePendingChanges = new RemotePendingChanges(generator, state);
    remotePendingChanges = new RemotePendingChanges(getServerState());
    // listen for changes on the configuration
    configuration.addChangeListener(this);
@@ -573,7 +404,6 @@
    DirectoryServer.registerAlertGenerator(this);
  }
  /**
   * Returns the base DN of this ReplicationDomain.
   *
@@ -741,7 +571,7 @@
    {
      // this isolation policy specifies that the updates are denied
      // when the broker is not connected.
      return broker.isConnected();
      return isConnected();
    }
    // we should never get there as the only possible policies are
    // ACCEPT_ALL_UPDATES and REJECT_ALL_UPDATES
@@ -931,322 +761,6 @@
  }
  /**
   * Receives an update message from the replicationServer.
   * also responsible for updating the list of pending changes
   * @return the received message - null if none
   */
  public UpdateMsg receive()
  {
    UpdateMsg update = null;
    while ( (update == null) && (!shutdown) )
    {
      InitializeRequestMsg initMsg = null;
      ReplicationMsg msg;
      try
      {
        msg = broker.receive();
        if (msg == null)
        {
          // The server is in the shutdown process
          return null;
        }
        if (debugEnabled())
          if (!(msg instanceof HeartbeatMsg))
            TRACER.debugVerbose("Message received <" + msg + ">");
        if (msg instanceof AckMsg)
        {
          AckMsg ack = (AckMsg) msg;
          receiveAck(ack);
        }
        else if (msg instanceof InitializeRequestMsg)
        {
          // Another server requests us to provide entries
          // for a total update
          initMsg = (InitializeRequestMsg)msg;
        }
        else if (msg instanceof InitializeTargetMsg)
        {
          // Another server is exporting its entries to us
          InitializeTargetMsg importMsg = (InitializeTargetMsg) msg;
          try
          {
            // This must be done while we are still holding the
            // broker lock because we are now going to receive a
            // bunch of entries from the remote server and we
            // want the import thread to catch them and
            // not the ListenerThread.
            initialize(importMsg);
          }
          catch(DirectoryException de)
          {
            // Returns an error message to notify the sender
            ErrorMsg errorMsg =
              new ErrorMsg(importMsg.getsenderID(),
                  de.getMessageObject());
            MessageBuilder mb = new MessageBuilder();
            mb.append(de.getMessageObject());
            TRACER.debugInfo(Message.toString(mb.toMessage()));
            broker.publish(errorMsg);
          }
        }
        else if (msg instanceof ErrorMsg)
        {
          if (ieContext != null)
          {
            // This is an error termination for the 2 following cases :
            // - either during an export
            // - or before an import really started
            //   For example, when we publish a request and the
            //  replicationServer did not find any import source.
            abandonImportExport((ErrorMsg)msg);
          }
          else
          {
            /*
             * Log error message
             */
            ErrorMsg errorMsg = (ErrorMsg)msg;
            logError(ERR_ERROR_MSG_RECEIVED.get(
                errorMsg.getDetails()));
          }
        }
        if (msg instanceof TopologyMsg)
        {
          TopologyMsg topoMsg = (TopologyMsg)msg;
          receiveTopo(topoMsg);
        }
        if (msg instanceof ChangeStatusMsg)
        {
          ChangeStatusMsg csMsg = (ChangeStatusMsg)msg;
          receiveChangeStatus(csMsg);
        }
        else if (msg instanceof UpdateMsg)
        {
          update = (UpdateMsg) msg;
          receiveUpdate(update);
        }
      }
      catch (SocketTimeoutException e)
      {
        // just retry
      }
      // Test if we have received and export request message and
      // if that's the case handle it now.
      // This must be done outside of the portion of code protected
      // by the broker lock so that we keep receiveing update
      // when we are doing and export and so that a possible
      // closure of the socket happening when we are publishing the
      // entries to the remote can be handled by the other
      // replay thread when they call this method and therefore the
      // broker.receive() method.
      if (initMsg != null)
      {
        // Do this work in a thread to allow replay thread continue working
        ExportThread exportThread = new ExportThread(initMsg.getsenderID());
        exportThread.start();
      }
    }
    return update;
  }
  /**
   * Processes an incoming TopologyMsg.
   * Updates the structures for the local view of the topology.
   *
   * @param topoMsg The topology information received from RS.
   */
  public void receiveTopo(TopologyMsg topoMsg)
  {
    if (debugEnabled())
      TRACER.debugInfo("Replication domain " + baseDn
        + " received topology info update:\n" + topoMsg);
    // Store new lists
    synchronized(getDsList())
    {
      synchronized(getRsList())
      {
        dsList = topoMsg.getDsList();
        rsList = topoMsg.getRsList();
      }
    }
  }
  /**
   * Set the initial status of the domain, once he is connected to the topology.
   * @param initStatus The status to enter the state machine with
   */
  public void setInitialStatus(ServerStatus initStatus)
  {
    // Sanity check: is it a valid initial status?
    if (!isValidInitialStatus(initStatus))
    {
      Message msg = ERR_DS_INVALID_INIT_STATUS.get(initStatus.toString(),
        baseDn.toString(), Short.toString(serverId));
      logError(msg);
    } else
    {
      status = initStatus;
    }
  }
  /**
   * Processes an incoming ChangeStatusMsg. Compute new status according to
   * given order. Then update domain for being compliant with new status
   * definition.
   * @param csMsg The received status message
   */
  private void receiveChangeStatus(ChangeStatusMsg csMsg)
  {
    if (debugEnabled())
      TRACER.debugInfo("Replication domain " + baseDn +
        " received change status message:\n" + csMsg);
    ServerStatus reqStatus = csMsg.getRequestedStatus();
    // Translate requested status to a state machine event
    StatusMachineEvent event = StatusMachineEvent.statusToEvent(reqStatus);
    if (event == StatusMachineEvent.INVALID_EVENT)
    {
      Message msg = ERR_DS_INVALID_REQUESTED_STATUS.get(reqStatus.toString(),
        baseDn.toString(), Short.toString(serverId));
      logError(msg);
      return;
    }
    // Compute new status and do matching tasks
    // Use synchronized as admin task (thread) could order to go in admin status
    // for instance (concurrent with receive thread).
    synchronized (status)
    {
      ServerStatus newStatus =
        StatusMachine.computeNewStatus(status, event);
      if (newStatus == ServerStatus.INVALID_STATUS)
      {
        Message msg = ERR_DS_CANNOT_CHANGE_STATUS.get(baseDn.toString(),
          Short.toString(serverId), status.toString(), event.toString());
        logError(msg);
        return;
      }
      // Store new status
      status = newStatus;
      if (debugEnabled())
      TRACER.debugInfo("Replication domain " + baseDn +
        " new status is: " + status);
      // Perform whatever actions are needed to apply properties for being
      // compliant with new status
      updateDomainForNewStatus();
    }
  }
  /**
   * Called when first connection or disconnection detected.
   */
  public void toNotConnectedStatus()
  {
    // Go into not connected status
    // Use synchronized as somebody could ask another status change at the same
    // time
    synchronized (status)
    {
      StatusMachineEvent event =
        StatusMachineEvent.TO_NOT_CONNECTED_STATUS_EVENT;
      ServerStatus newStatus =
        StatusMachine.computeNewStatus(status, event);
      if (newStatus == ServerStatus.INVALID_STATUS)
      {
        Message msg = ERR_DS_CANNOT_CHANGE_STATUS.get(baseDn.toString(),
          Short.toString(serverId), status.toString(), event.toString());
        logError(msg);
        return;
      }
      // Store new status
      status = newStatus;
      if (debugEnabled())
        TRACER.debugInfo("Replication domain " + baseDn +
          " new status is: " + status);
      // Perform whatever actions are needed to apply properties for being
      // compliant with new status
      updateDomainForNewStatus();
    }
  }
  /**
   * Perform whatever actions are needed to apply properties for being
   * compliant with new status. Must be called in synchronized section for
   * status. The new status is already set in status variable.
   */
  private void updateDomainForNewStatus()
  {
    switch (status)
    {
      case NOT_CONNECTED_STATUS:
        break;
      case NORMAL_STATUS:
        break;
      case DEGRADED_STATUS:
        break;
      case FULL_UPDATE_STATUS:
        // Signal RS we just entered the full update status
        broker.signalStatusChange(status);
        break;
      case BAD_GEN_ID_STATUS:
        break;
      default:
        if (debugEnabled())
          TRACER.debugInfo("updateDomainForNewStatus: unexpected status: " +
            status);
    }
  }
  /**
   * Do the necessary processing when an UpdateMsg was received.
   *
   * @param update The received UpdateMsg.
   */
  public void receiveUpdate(UpdateMsg update)
  {
    remotePendingChanges.putRemoteUpdate(update);
    numRcvdUpdates.incrementAndGet();
  }
  /**
   * Do the necessary processing when an AckMsg is received.
   *
   * @param ack The AckMsg that was received.
   */
  public void receiveAck(AckMsg ack)
  {
    UpdateMsg update;
    ChangeNumber changeNumber = ack.getChangeNumber();
    synchronized (waitingAckMsgs)
    {
      update = waitingAckMsgs.remove(changeNumber);
    }
    if (update != null)
    {
      synchronized (update)
      {
        update.notify();
      }
    }
  }
  /**
   * Check if an operation must be synchronized.
   * Also update the list of pending changes and the server RUV
   * @param op the operation
@@ -1258,19 +772,17 @@
    {
      numReplayedPostOpCalled++;
    }
    UpdateMsg msg = null;
    LDAPUpdateMsg msg = null;
    // Note that a failed non-replication operation might not have a change
    // number.
    ChangeNumber curChangeNumber = OperationContext.getChangeNumber(op);
    boolean isAssured = isAssured(op);
    if ((result == ResultCode.SUCCESS) && (!op.isSynchronizationOperation()))
    {
      // Generate a replication message for a successful non-replication
      // operation.
      msg = UpdateMsg.generateMsg(op);
      msg = LDAPUpdateMsg.generateMsg(op);
      if (msg == null)
      {
@@ -1307,16 +819,6 @@
        return;
      }
      if (msg != null && isAssured)
      {
        synchronized (waitingAckMsgs)
        {
          // Add the assured message to the list of update that are
          // waiting acknowledgements
          waitingAckMsgs.put(curChangeNumber, msg);
        }
      }
      if (generationIdSavedStatus != true)
      {
        this.saveGenerationId(generationId);
@@ -1334,53 +836,8 @@
    if (!op.isSynchronizationOperation())
    {
      int pushedChanges = pendingChanges.pushCommittedChanges();
      numSentUpdates.addAndGet(pushedChanges);
      pendingChanges.pushCommittedChanges();
    }
    // Wait for acknowledgement of an assured message.
    if (msg != null && isAssured)
    {
      synchronized (msg)
      {
        while (waitingAckMsgs.containsKey(msg.getChangeNumber()))
        {
          // TODO : should have a configurable timeout to get
          // out of this loop
          try
          {
            msg.wait(1000);
          } catch (InterruptedException e)
          { }
        }
      }
    }
  }
  /**
   * get the number of updates received by the replication plugin.
   *
   * @return the number of updates received
   */
  public int getNumRcvdUpdates()
  {
    if (numRcvdUpdates != null)
      return numRcvdUpdates.get();
    else
      return 0;
  }
  /**
   * Get the number of updates sent by the replication plugin.
   *
   * @return the number of updates sent
   */
  public int getNumSentUpdates()
  {
    if (numSentUpdates != null)
      return numSentUpdates.get();
    else
      return 0;
  }
  /**
@@ -1397,27 +854,6 @@
  }
  /**
   * Increment the number of processed updates.
   */
  public void incProcessedUpdates()
  {
    numProcessedUpdates.incrementAndGet();
  }
  /**
   * get the number of updates replayed by the replication.
   *
   * @return The number of updates replayed by the replication
   */
  public int getNumProcessedUpdates()
  {
    if (numProcessedUpdates != null)
      return numProcessedUpdates.get();
    else
      return 0;
  }
  /**
   * get the number of updates replayed successfully by the replication.
   *
   * @return The number of updates replayed successfully
@@ -1428,16 +864,6 @@
  }
  /**
   * get the ServerState.
   *
   * @return the ServerState
   */
  public ServerState getServerState()
  {
    return state;
  }
  /**
   * Get the debugCount.
   *
   * @return Returns the debugCount.
@@ -1448,49 +874,6 @@
  }
  /**
   * Send an Ack message.
   *
   * @param changeNumber The ChangeNumber for which the ack must be sent.
   */
  public void ack(ChangeNumber changeNumber)
  {
    broker.publish(new AckMsg(changeNumber));
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void run()
  {
    done = false;
    // Create the listener thread
    listenerThread = new ListenerThread(this, updateToReplayQueue);
    listenerThread.start();
    while (shutdown  == false)
    {
      try
      {
        synchronized (this)
        {
          this.wait(1000);
          if (!disabled && !stateSavingDisabled )
          {
            // save the RUV
            state.save();
          }
        }
      } catch (InterruptedException e)
      { }
    }
    state.save();
    done = true;
  }
  /**
   * Shutdown this ReplicationDomain.
   */
  public void shutdown()
@@ -1498,27 +881,19 @@
    // stop the flush thread
    shutdown = true;
    // Stop the listener thread
    if (listenerThread != null)
    // stop the thread in charge of flushing the ServerState.
    if (flushThread != null)
    {
      listenerThread.shutdown();
      synchronized (flushThread)
      {
        flushThread.notify();
      }
    }
    synchronized (this)
    {
      this.notify();
    }
    DirectoryServer.deregisterMonitorProvider(monitor.getMonitorInstanceName());
    DirectoryServer.deregisterAlertGenerator(this);
    // stop the ReplicationBroker
    broker.stop();
    // Wait for the listener thread to stop
    if (listenerThread != null)
      listenerThread.waitForShutdown();
    // stop the ReplicationDomain
    stopDomain();
    // wait for completion of the persistentServerState thread.
    try
@@ -1534,26 +909,11 @@
  }
  /**
   * Get the name of the replicationServer to which this domain is currently
   * connected.
   *
   * @return the name of the replicationServer to which this domain
   *         is currently connected.
   */
  public String getReplicationServer()
  {
    if (broker != null)
      return broker.getReplicationServer();
    else
      return "Not connected";
  }
  /**
   * Create and replay a synchronized Operation from an UpdateMsg.
   *
   * @param msg The UpdateMsg to be replayed.
   */
  public void replay(UpdateMsg msg)
  public void replay(LDAPUpdateMsg msg)
  {
    Operation op = null;
    boolean done = false;
@@ -1565,6 +925,7 @@
    // whose dependency has been replayed until no more left.
    do
    {
      String replayErrorMsg = null;
      try
      {
        op = msg.createOperation(conn);
@@ -1572,12 +933,12 @@
        while ((!dependency) && (!done) && (retryCount-- > 0))
        {
          // Try replay the operation
          op.setInternalOperation(true);
          op.setSynchronizationOperation(true);
          changeNumber = OperationContext.getChangeNumber(op);
          ((AbstractOperation) op).run();
          // Try replay the operation
          ResultCode result = op.getResultCode();
          if (result != ResultCode.SUCCESS)
@@ -1638,7 +999,7 @@
            op.getErrorMessage().toString());
          logError(message);
          numUnresolvedNamingConflicts.incrementAndGet();
          replayErrorMsg = message.toString();
          updateError(changeNumber);
        }
      } catch (ASN1Exception e)
@@ -1646,16 +1007,19 @@
        Message message = ERR_EXCEPTION_DECODING_OPERATION.get(
          String.valueOf(msg) + stackTraceToSingleLineString(e));
        logError(message);
        replayErrorMsg = message.toString();
      } catch (LDAPException e)
      {
        Message message = ERR_EXCEPTION_DECODING_OPERATION.get(
          String.valueOf(msg) + stackTraceToSingleLineString(e));
        logError(message);
        replayErrorMsg = message.toString();
      } catch (DataFormatException e)
      {
        Message message = ERR_EXCEPTION_DECODING_OPERATION.get(
          String.valueOf(msg) + stackTraceToSingleLineString(e));
        logError(message);
        replayErrorMsg = message.toString();
      } catch (Exception e)
      {
        if (changeNumber != null)
@@ -1669,21 +1033,20 @@
          Message message = ERR_EXCEPTION_REPLAYING_OPERATION.get(
            stackTraceToSingleLineString(e), op.toString());
          logError(message);
          replayErrorMsg = message.toString();
          updateError(changeNumber);
        } else
        {
          Message message = ERR_EXCEPTION_DECODING_OPERATION.get(
            String.valueOf(msg) + stackTraceToSingleLineString(e));
          logError(message);
          replayErrorMsg = message.toString();
        }
      } finally
      {
        if (!dependency)
        {
          broker.updateWindowAfterReplay();
          if (msg.isAssured())
            ack(msg.getChangeNumber());
          incProcessedUpdates();
          processUpdateDone(msg, replayErrorMsg);
        }
      }
@@ -1913,7 +1276,7 @@
  * @return true if the process is completed, false if it must continue..
  */
 private boolean solveNamingConflict(DeleteOperation op,
     UpdateMsg msg)
     LDAPUpdateMsg msg)
 {
   ResultCode result = op.getResultCode();
   DeleteContext ctx = (DeleteContext) op.getAttachment(SYNCHROCONTEXT);
@@ -1983,7 +1346,7 @@
 * @throws Exception When the operation is not valid.
 */
private boolean solveNamingConflict(ModifyDNOperation op,
    UpdateMsg msg) throws Exception
    LDAPUpdateMsg msg) throws Exception
{
  ResultCode result = op.getResultCode();
  ModifyDnContext ctx = (ModifyDnContext) op.getAttachment(SYNCHROCONTEXT);
@@ -2391,83 +1754,6 @@
  }
  /**
   * Check if an operation must be processed as an assured operation.
   *
   * @param op the operation to be checked.
   * @return true if the operations must be processed as an assured operation.
   */
  private boolean isAssured(PostOperationOperation op)
  {
    // TODO : should have a filtering mechanism for checking
    // operation that are assured and operations that are not.
    return false;
  }
  /**
   * Get the maximum receive window size.
   *
   * @return The maximum receive window size.
   */
  public int getMaxRcvWindow()
  {
    if (broker != null)
      return broker.getMaxRcvWindow();
    else
      return 0;
  }
  /**
   * Get the current receive window size.
   *
   * @return The current receive window size.
   */
  public int getCurrentRcvWindow()
  {
    if (broker != null)
      return broker.getCurrentRcvWindow();
    else
      return 0;
  }
  /**
   * Get the maximum send window size.
   *
   * @return The maximum send window size.
   */
  public int getMaxSendWindow()
  {
    if (broker != null)
      return broker.getMaxSendWindow();
    else
      return 0;
  }
  /**
   * Get the current send window size.
   *
   * @return The current send window size.
   */
  public int getCurrentSendWindow()
  {
    if (broker != null)
      return broker.getCurrentSendWindow();
    else
      return 0;
  }
  /**
   * Get the number of times the replication connection was lost.
   * @return The number of times the replication connection was lost.
   */
  public int getNumLostConnections()
  {
    if (broker != null)
      return broker.getNumLostConnections();
    else
      return 0;
  }
  /**
   * Get the number of modify conflicts successfully resolved.
   * @return The number of modify conflicts successfully resolved.
   */
@@ -2477,7 +1763,7 @@
  }
  /**
   * Get the number of namign conflicts successfully resolved.
   * Get the number of naming conflicts successfully resolved.
   * @return The number of naming conflicts successfully resolved.
   */
  public int getNumResolvedNamingConflicts()
@@ -2495,18 +1781,9 @@
  }
  /**
   * Get the server ID.
   * @return The server ID.
   */
  public short getServerId()
  {
    return serverId;
  }
  /**
   * Check if the domain solve conflicts.
   *
   * @return a boolean indicating if the domain should sove conflicts.
   * @return a boolean indicating if the domain should solve conflicts.
   */
  public boolean solveConflict()
  {
@@ -2526,16 +1803,7 @@
    state.save();
    state.clearInMemory();
    disabled = true;
    // Stop the listener thread
    if (listenerThread != null)
      listenerThread.shutdown();
    broker.stop(); // This will cut the session and wake up the listener
    // Wait for the listener thread to stop
    if (listenerThread != null)
      listenerThread.waitForShutdown();
    disableService(); // This will cut the session and wake up the listener
  }
  /**
@@ -2578,16 +1846,7 @@
      return;
    }
    // After an on-line import, the value of the generationId is new
    // and it is necessary for the broker to send this new value as part
    // of the serverStart message.
    broker.setGenerationId(generationId);
    broker.start(replicationServers);
    // Create the listener thread
    listenerThread = new ListenerThread(this, updateToReplayQueue);
    listenerThread.start();
    enableService();
    disabled = false;
  }
@@ -2600,7 +1859,7 @@
   */
  public long computeGenerationId() throws DirectoryException
  {
    long genId = exportBackend(true);
    long genId = exportBackend(null, true);
    if (debugEnabled())
      TRACER.debugInfo("Computed generationId: generationId=" + genId);
@@ -2609,11 +1868,9 @@
  }
  /**
   * Returns the generationId set for this domain.
   *
   * @return The generationId.
   * {@inheritDoc}
   */
  public long getGenerationId()
  public long getGenerationID()
  {
    return generationId;
  }
@@ -2627,7 +1884,7 @@
  /**
   * Stores the value of the generationId.
   * @param generationId The value of the generationId.
   * @return a ResultCode indicating if the method was successfull.
   * @return a ResultCode indicating if the method was successful.
   */
  public ResultCode saveGenerationId(long generationId)
  {
@@ -2792,45 +2049,8 @@
  }
  /**
   * Reset the generationId of this domain in the whole topology.
   * A message is sent to the Replication Servers for them to reset
   * their change dbs.
   *
   * @param generationIdNewValue The new value of the generation Id.
   * @throws DirectoryException when an error occurs
   */
  public void resetGenerationId(Long generationIdNewValue)
  throws DirectoryException
  {
    if (debugEnabled())
      TRACER.debugInfo(
          this.getName() + "resetGenerationId" + generationIdNewValue);
    if (!isConnected())
    {
      ResultCode resultCode = ResultCode.OTHER;
      Message message = ERR_RESET_GENERATION_CONN_ERR_ID.get(
          baseDn.toNormalizedString());
      throw new DirectoryException(
         resultCode, message);
    }
    ResetGenerationIdMsg genIdMessage = null;
    if (generationIdNewValue == null)
    {
      genIdMessage = new ResetGenerationIdMsg(this.generationId);
    }
    else
    {
      genIdMessage = new ResetGenerationIdMsg(generationIdNewValue);
    }
    broker.publish(genIdMessage);
  }
  /**
   * Do whatever is needed when a backup is started.
   * We need to make sure that the serverState is correclty save.
   * We need to make sure that the serverState is correctly save.
   */
  public void backupStart()
  {
@@ -2849,103 +2069,7 @@
   * Total Update >>
   */
  /**
   * Receives bytes related to an entry in the context of an import to
   * initialize the domain (called by ReplLDIFInputStream).
   *
   * @return The bytes. Null when the Done or Err message has been received
   */
  public byte[] receiveEntryBytes()
  {
    ReplicationMsg msg;
    while (true)
    {
      try
      {
        msg = broker.receive();
        if (debugEnabled())
          TRACER.debugVerbose(
              " sid:" + this.serverId +
              " base DN:" + this.baseDn +
              " Import EntryBytes received " + msg);
        if (msg == null)
        {
          // The server is in the shutdown process
          return null;
        }
        if (msg instanceof EntryMsg)
        {
          EntryMsg entryMsg = (EntryMsg)msg;
          byte[] entryBytes = entryMsg.getEntryBytes();
          ieContext.updateCounters();
          return entryBytes;
        }
        else if (msg instanceof DoneMsg)
        {
          // This is the normal termination of the import
          // No error is stored and the import is ended
          // by returning null
          return null;
        }
        else if (msg instanceof ErrorMsg)
        {
          // This is an error termination during the import
          // The error is stored and the import is ended
          // by returning null
          ErrorMsg errorMsg = (ErrorMsg)msg;
          ieContext.exception = new DirectoryException(
                                      ResultCode.OTHER,
                                      errorMsg.getDetails());
          return null;
        }
        else
        {
          // Other messages received during an import are trashed
        }
      }
      catch(Exception e)
      {
        // TODO: i18n
        ieContext.exception = new DirectoryException(ResultCode.OTHER,
            Message.raw("received an unexpected message type" +
                e.getLocalizedMessage()));
      }
    }
  }
  /**
   * Processes an error message received while an import/export is
   * on going.
   * @param errorMsg The error message received.
   */
  protected void abandonImportExport(ErrorMsg errorMsg)
  {
    // FIXME TBD Treat the case where the error happens while entries
    // are being exported
    if (debugEnabled())
      TRACER.debugVerbose(
          " abandonImportExport:" + this.serverId +
          " base DN:" + this.baseDn +
          " Error Msg received " + errorMsg);
    if (ieContext != null)
    {
      ieContext.exception = new DirectoryException(ResultCode.OTHER,
          errorMsg.getDetails());
      if (ieContext.initializeTask instanceof InitializeTask)
      {
        // Update the task that initiated the import
        ((InitializeTask)ieContext.initializeTask).
        updateTaskCompletionState(ieContext.exception);
        releaseIEContext();
      }
    }
  }
  /**
   * Clears all the entries from the JE backend determined by the
@@ -3000,16 +2124,31 @@
  }
  /**
   * This method trigger an export of the replicated data.
   *
   * @param output               The OutputStream where the export should
   *                             be produced.
   * @throws DirectoryException  When needed.
   */
  protected void exportBackend(OutputStream output) throws DirectoryException
  {
    exportBackend(output, false);
  }
  /**
   * Export the entries from the backend and/or compute the generation ID.
   * The ieContext must have been set before calling.
   * @param checksumOutput true is the exportBackend is called to compute
   *                       the generationID
   *
   * @return The computed  generationID.
   * @param output              The OutputStream where the export should
   *                            be produced.
   * @param checksumOutput      A boolean indicating if this export is
   *                            invoked to perform a checksum only
   *
   * @return The computed       GenerationID.
   *
   * @throws DirectoryException when an error occurred
   */
  protected long exportBackend(boolean checksumOutput)
  protected long exportBackend(OutputStream output, boolean checksumOutput)
  throws DirectoryException
  {
    long genID = 0;
@@ -3042,7 +2181,7 @@
    }
    OutputStream os;
    ReplLDIFOutputStream ros;
    ReplLDIFOutputStream ros = null;
    if (checksumOutput)
    {
@@ -3060,8 +2199,7 @@
    }
    else
    {
      ros = new ReplLDIFOutputStream(this, (short)-1);
      os = ros;
      os = output;
    }
    LDIFExportConfig exportConfig = new LDIFExportConfig(os);
@@ -3096,7 +2234,7 @@
    }
    catch (DirectoryException de)
    {
      if ((checksumOutput) &&
      if ((ros != null) &&
          (ros.getNumExportedEntries() >= entryCount))
      {
        // This is the normal end when computing the generationId
@@ -3170,277 +2308,6 @@
  }
  /**
   * Get the internal broker to perform some operations on it.
   *
   * @return The broker for this domain.
   */
  ReplicationBroker getBroker()
  {
    return broker;
  }
  /**
   * Exports an entry in LDIF format.
   *
   * @param  lDIFEntry The entry to be exported..
   *
   * @throws IOException when an error occurred.
   */
  public void exportLDIFEntry(String lDIFEntry) throws IOException
  {
    // If an error was raised - like receiving an ErrorMsg
    // we just let down the export.
    if (ieContext.exception != null)
    {
      IOException ioe = new IOException(ieContext.exception.getMessage());
      ieContext = null;
      throw ioe;
    }
    EntryMsg entryMessage = new EntryMsg(
        serverId, ieContext.exportTarget, lDIFEntry.getBytes());
    broker.publish(entryMessage);
    try
    {
      ieContext.updateCounters();
    }
    catch (DirectoryException de)
    {
      throw new IOException(de.getMessage());
    }
  }
  /**
   * Initializes this domain from another source server.
   *
   * @param source The source from which to initialize
   * @param initTask The task that launched the initialization
   *                 and should be updated of its progress.
   * @throws DirectoryException when an error occurs
   */
  public void initializeFromRemote(short source, Task initTask)
  throws DirectoryException
  {
    if (debugEnabled())
      TRACER.debugInfo("Entering initializeFromRemote");
    acquireIEContext();
    ieContext.initializeTask = initTask;
    InitializeRequestMsg initializeMsg = new InitializeRequestMsg(
        baseDn, serverId, source);
    // Publish Init request msg
    broker.publish(initializeMsg);
    // .. we expect to receive entries or err after that
  }
  /**
   * Verifies that the given string represents a valid source
   * from which this server can be initialized.
   * @param sourceString The string representing the source
   * @return The source as a short value
   * @throws DirectoryException if the string is not valid
   */
  public short decodeSource(String sourceString)
  throws DirectoryException
  {
    short  source = 0;
    Throwable cause = null;
    try
    {
      source = Integer.decode(sourceString).shortValue();
      if ((source >= -1) && (source != serverId))
      {
        // TODO Verifies serverID is in the domain
        // We shold check here that this is a server implied
        // in the current domain.
        return source;
      }
    }
    catch(Exception e)
    {
      cause = e;
    }
    ResultCode resultCode = ResultCode.OTHER;
    Message message = ERR_INVALID_IMPORT_SOURCE.get();
    if (cause != null)
    {
      throw new DirectoryException(
          resultCode, message, cause);
    }
    else
    {
      throw new DirectoryException(
          resultCode, message);
    }
  }
  /**
   * Verifies that the given string represents a valid source
   * from which this server can be initialized.
   * @param targetString The string representing the source
   * @return The source as a short value
   * @throws DirectoryException if the string is not valid
   */
  public short decodeTarget(String targetString)
  throws DirectoryException
  {
    short  target = 0;
    Throwable cause;
    if (targetString.equalsIgnoreCase("all"))
    {
      return RoutableMsg.ALL_SERVERS;
    }
    // So should be a serverID
    try
    {
      target = Integer.decode(targetString).shortValue();
      if (target >= 0)
      {
        // FIXME Could we check now that it is a know server in the domain ?
      }
      return target;
    }
    catch(Exception e)
    {
      cause = e;
    }
    ResultCode resultCode = ResultCode.OTHER;
    Message message = ERR_INVALID_EXPORT_TARGET.get();
    if (cause != null)
      throw new DirectoryException(
          resultCode, message, cause);
    else
      throw new DirectoryException(
          resultCode, message);
  }
  private synchronized void acquireIEContext()
  throws DirectoryException
  {
    if (ieContext != null)
    {
      // Rejects 2 simultaneous exports
      Message message = ERR_SIMULTANEOUS_IMPORT_EXPORT_REJECTED.get();
      throw new DirectoryException(ResultCode.OTHER,
          message);
    }
    ieContext = new IEContext();
  }
  private synchronized void releaseIEContext()
  {
    ieContext = null;
  }
  /**
   * Process the initialization of some other server or servers in the topology
   * specified by the target argument.
   * @param target The target that should be initialized
   * @param initTask The task that triggers this initialization and that should
   *                 be updated with its progress.
   *
   * @exception DirectoryException When an error occurs.
   */
  public void initializeRemote(short target, Task initTask)
  throws DirectoryException
  {
    initializeRemote(target, serverId, initTask);
  }
  /**
   * Process the initialization of some other server or servers in the topology
   * specified by the target argument when this initialization specifying the
   * server that requests the initialization.
   *
   * @param target The target that should be initialized.
   * @param requestorID The server that initiated the export.
   * @param initTask The task that triggers this initialization and that should
   *  be updated with its progress.
   *
   * @exception DirectoryException When an error occurs.
   */
  public void initializeRemote(short target, short requestorID, Task initTask)
  throws DirectoryException
  {
    Message msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_START.get(
      Short.toString(serverId),
      baseDn.toString(),
      Short.toString(requestorID));
    logError(msg);
    boolean contextAcquired=false;
    try
    {
      Backend backend = retrievesBackend(this.baseDn);
      if (!backend.supportsLDIFExport())
      {
        Message message = ERR_INIT_EXPORT_NOT_SUPPORTED.get(
                            backend.getBackendID().toString());
        logError(message);
        throw new DirectoryException(ResultCode.OTHER, message);
      }
      acquireIEContext();
      contextAcquired = true;
      // The number of entries to be exported is the number of entries under
      // the base DN entry and the base entry itself.
      long entryCount = backend.numSubordinates(baseDn, true) + 1;
      ieContext.exportTarget = target;
      if (initTask != null)
      {
        ieContext.initializeTask = initTask;
      }
      ieContext.setCounters(entryCount, entryCount);
      // Send start message to the peer
      InitializeTargetMsg initializeMessage = new InitializeTargetMsg(
          baseDn, serverId, ieContext.exportTarget, requestorID, entryCount);
      broker.publish(initializeMessage);
      exportBackend(false);
      // Notify the peer of the success
      DoneMsg doneMsg = new DoneMsg(serverId,
          initializeMessage.getDestination());
      broker.publish(doneMsg);
      releaseIEContext();
    }
    catch(DirectoryException de)
    {
      // Notify the peer of the failure
      ErrorMsg errorMsg =
        new ErrorMsg(target,
                         de.getMessageObject());
      broker.publish(errorMsg);
      if (contextAcquired)
        releaseIEContext();
      throw(de);
    }
    msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_END.get(
      Short.toString(serverId),
      baseDn.toString(),
      Short.toString(requestorID));
    logError(msg);
  }
  /**
   * Process backend before import.
   * @param backend The backend.
   * @throws Exception
@@ -3468,51 +2335,16 @@
  }
  /**
   * Initializes the domain's backend with received entries.
   * @param initializeMessage The message that initiated the import.
   * @exception DirectoryException Thrown when an error occurs.
   * This method should trigger an import of the replicated data.
   *
   * @param input                The InputStream from which
   * @throws DirectoryException  When needed.
   */
  protected void initialize(InitializeTargetMsg initializeMessage)
  throws DirectoryException
  public void importBackend(InputStream input) throws DirectoryException
  {
    LDIFImportConfig importConfig = null;
    DirectoryException de = null;
    Message msg = NOTE_FULL_UPDATE_ENGAGED_FROM_REMOTE_START.get(
      Short.toString(serverId),
      baseDn.toString(),
      Long.toString(initializeMessage.getRequestorID()));
    logError(msg);
    // Go into full update status
    // Use synchronized as somebody could ask another status change at the same
    // time
    synchronized (status)
    {
      StatusMachineEvent event = StatusMachineEvent.TO_FULL_UPDATE_STATUS_EVENT;
      ServerStatus newStatus =
        StatusMachine.computeNewStatus(status, event);
      if (newStatus == ServerStatus.INVALID_STATUS)
      {
        msg = ERR_DS_CANNOT_CHANGE_STATUS.get(baseDn.toString(),
          Short.toString(serverId), status.toString(), event.toString());
        logError(msg);
        return;
      }
      // Store new status
      status = newStatus;
      if (debugEnabled())
      TRACER.debugInfo("Replication domain " + baseDn +
        " new status is: " + status);
      // Perform whatever actions are needed to apply properties for being
      // compliant with new status
      updateDomainForNewStatus();
    }
    Backend backend = retrievesBackend(baseDn);
    try
@@ -3526,26 +2358,8 @@
      }
      else
      {
        if (initializeMessage.getRequestorID() == serverId)
        {
          // The import responds to a request we did so the IEContext
          // is already acquired
        }
        else
        {
          acquireIEContext();
        }
        ieContext.importSource = initializeMessage.getsenderID();
        ieContext.entryLeftCount = initializeMessage.getEntryCount();
        ieContext.setCounters(initializeMessage.getEntryCount(),
            initializeMessage.getEntryCount());
        preBackendImport(backend);
        ieContext.ldifImportInputStream = new ReplLDIFInputStream(this);
        importConfig =
          new LDIFImportConfig(ieContext.ldifImportInputStream);
          new LDIFImportConfig(input);
        List<DN> includeBranches = new ArrayList<DN>();
        includeBranches.add(this.baseDn);
        importConfig.setIncludeBranches(includeBranches);
@@ -3553,11 +2367,12 @@
        // TODO How to deal with rejected entries during the import
        importConfig.writeRejectedEntries(
          getFileForPath("logs" + File.separator +
              "replInitRejectedEntries").getAbsolutePath(),
          ExistingFileBehavior.OVERWRITE);
            getFileForPath("logs" + File.separator +
            "replInitRejectedEntries").getAbsolutePath(),
            ExistingFileBehavior.OVERWRITE);
        // Process import
        preBackendImport(backend);
        backend.importLDIF(importConfig);
        stateSavingDisabled = false;
@@ -3570,9 +2385,6 @@
    }
    finally
    {
      if ((ieContext != null)  && (ieContext.exception != null))
        de = ieContext.exception;
      // Cleanup
      if (importConfig != null)
      {
@@ -3592,7 +2404,6 @@
          TRACER.debugInfo(
              "After import, the replication plugin restarts connections" +
              " to all RSs to provide new generation ID=" + generationId);
        broker.setGenerationId(generationId);
      }
      catch (DirectoryException fe)
      {
@@ -3604,29 +2415,12 @@
        if (de == null)
          de = fe;
      }
      // Re-exchange generationID and state with RS
      broker.reStart();
      // Update the task that initiated the import
      if ((ieContext != null ) && (ieContext.initializeTask != null))
      {
        ((InitializeTask)ieContext.initializeTask).
        updateTaskCompletionState(de);
      }
      releaseIEContext();
    }
    // Sends up the root error.
    if (de != null)
    {
      throw de;
    }
    msg = NOTE_FULL_UPDATE_ENGAGED_FROM_REMOTE_END.get(
      Short.toString(serverId),
      baseDn.toString(),
      Long.toString(initializeMessage.getRequestorID()));
    logError(msg);
  }
  /**
@@ -3660,10 +2454,10 @@
   * @throws DirectoryException When an error occurred or no domain
   * match the provided baseDn.
   */
  public static ReplicationDomain retrievesReplicationDomain(DN baseDn)
  public static LDAPReplicationDomain retrievesReplicationDomain(DN baseDn)
  throws DirectoryException
  {
    ReplicationDomain replicationDomain = null;
    LDAPReplicationDomain replicationDomain = null;
    // Retrieves the domain
    DirectoryServer.getSynchronizationProviders();
@@ -3678,7 +2472,7 @@
      }
      // From the domainDN retrieves the replication domain
      ReplicationDomain sdomain =
      LDAPReplicationDomain sdomain =
        MultimasterReplication.findDomain(baseDn, null);
      if (sdomain == null)
      {
@@ -3714,15 +2508,6 @@
    return retrievesBackend(baseDn);
  }
  /**
   * Returns a boolean indicating if an import or export is currently
   * processed.
   * @return The status
   */
  public boolean ieRunning()
  {
    return (ieContext != null);
  }
  /*
   * <<Total Update
   */
@@ -3769,7 +2554,7 @@
  {
    // Check that there is not already a domain with the same DN
    DN dn = configuration.getBaseDN();
    ReplicationDomain domain = MultimasterReplication.findDomain(dn, null);
    LDAPReplicationDomain domain = MultimasterReplication.findDomain(dn, null);
    if ((domain != null) && (domain.baseDn.equals(dn)))
    {
      Message message = ERR_SYNC_INVALID_DN.get();
@@ -3793,37 +2578,12 @@
  public ConfigChangeResult applyConfigurationChange(
         ReplicationDomainCfg configuration)
  {
    // server id and base dn are readonly.
    // isolationPolicy can be set immediately and will apply
    // to the next updates.
    // The other parameters needs to be renegociated with the ReplicationServer
    // so that requires restarting the session with the ReplicationServer.
    Boolean needToRestartSession = false;
    Collection<String> newReplServers = configuration.getReplicationServer();
    // A new session is necessary only when information regarding
    // the connection is modified
    if ((!(replicationServers.size() == newReplServers.size()
        && replicationServers.containsAll(newReplServers))) ||
        window != configuration.getWindowSize() ||
        heartbeatInterval != configuration.getHeartbeatInterval())
      needToRestartSession = true;
    replicationServers = newReplServers;
    window = configuration.getWindowSize();
    heartbeatInterval = configuration.getHeartbeatInterval();
    broker.changeConfig(replicationServers, maxReceiveQueue, maxReceiveDelay,
        maxSendQueue, maxSendDelay, window, heartbeatInterval);
    isolationpolicy = configuration.getIsolationPolicy();
    // To be able to stop and restart the broker properly just
    // disable and enable the domain. That way a new session
    // with the new configuration is available.
    if (needToRestartSession)
    {
      this.disable();
      this.enable();
    }
    changeConfig(
        configuration.getReplicationServer(),
        configuration.getWindowSize(),
        configuration.getHeartbeatInterval());
    return new ConfigChangeResult(ResultCode.SUCCESS, false);
  }
@@ -3867,101 +2627,265 @@
  }
  /**
   * Check if the domain is connected to a ReplicationServer.
   * Starts the Replication Domain.
   */
  public void start()
  {
    // Create the ServerStateFlush thread
    flushThread = new ServerStateFlush();
    flushThread.start();
    startListenService();
  }
  /**
   * {@inheritDoc}
   */
  public void sessionInitiated(
      ServerStatus initStatus,
      ServerState replicationServerState,
      ProtocolSession session)
  {
    super.sessionInitiated(initStatus, replicationServerState, session);
    try
    {
      /*
       * We must not publish changes to a replicationServer that has
       * not seen all our previous changes because this could cause
       * some other ldap servers to miss those changes.
       * Check that the ReplicationServer has seen all our previous
       * changes.
       */
      ChangeNumber replServerMaxChangeNumber =
        replicationServerState.getMaxChangeNumber(serverId);
      if (replServerMaxChangeNumber == null)
      {
        replServerMaxChangeNumber = new ChangeNumber(0, 0, serverId);
      }
      ChangeNumber ourMaxChangeNumber =
        state.getMaxChangeNumber(serverId);
      if ((ourMaxChangeNumber != null) &&
          (!ourMaxChangeNumber.olderOrEqual(replServerMaxChangeNumber)))
      {
        // Replication server is missing some of our changes: let's
        // send them to him.
        Message message = DEBUG_GOING_TO_SEARCH_FOR_CHANGES.get();
        logError(message);
        /*
         * Get all the changes that have not been seen by this
         * replication server and populate the replayOperations
         * list.
         */
        InternalSearchOperation op = searchForChangedEntries(
            baseDn, replServerMaxChangeNumber, this);
        if (op.getResultCode() != ResultCode.SUCCESS)
        {
          /*
           * An error happened trying to search for the updates
           * This server will start accepting again new updates but
           * some inconsistencies will stay between servers.
           * Log an error for the repair tool
           * that will need to re-synchronize the servers.
           */
          message = ERR_CANNOT_RECOVER_CHANGES.get(
              baseDn.toNormalizedString());
          logError(message);
        } else
        {
          for (FakeOperation replayOp : replayOperations)
          {
            ChangeNumber cn = replayOp.getChangeNumber();
            /*
             * Because the entry returned by the search operation
             * can contain old historical information, it is
             * possible that some of the FakeOperation are
             * actually older than the
             * Only send the Operation if it was newer than
             * the last ChangeNumber known by the Replication Server.
             */
            if (cn.newer(replServerMaxChangeNumber))
            {
              message =
                DEBUG_SENDING_CHANGE.get(
                    replayOp.getChangeNumber().toString());
              logError(message);
              session.publish(replayOp.generateMessage());
            }
          }
          message = DEBUG_CHANGES_SENT.get();
          logError(message);
        }
        replayOperations.clear();
      }
    } catch (Exception e)
    {
      Message message = ERR_PUBLISHING_FAKE_OPS.get(
          baseDn.toNormalizedString(),
          e.getLocalizedMessage() + stackTraceToSingleLineString(e));
      logError(message);
    }
  }
  /**
   * Search for the changes that happened since fromChangeNumber
   * based on the historical attribute. The only changes that will
   * be send will be the one generated on the serverId provided in
   * fromChangeNumber.
   * @param baseDn the base DN
   * @param fromChangeNumber The change number from which we want the changes
   * @param resultListener that will process the entries returned.
   * @return the internal search operation
   * @throws Exception when raised.
   */
  public static InternalSearchOperation searchForChangedEntries(
    DN baseDn,
    ChangeNumber fromChangeNumber,
    InternalSearchListener resultListener)
    throws Exception
  {
    InternalClientConnection conn =
      InternalClientConnection.getRootConnection();
    Short serverId = fromChangeNumber.getServerId();
    String maxValueForId = "ffffffffffffffff" +
      String.format("%04x", serverId) + "ffffffff";
    LDAPFilter filter = LDAPFilter.decode(
       "(&(" + Historical.HISTORICALATTRIBUTENAME + ">=dummy:"
       + fromChangeNumber + ")(" + Historical.HISTORICALATTRIBUTENAME +
       "<=dummy:" + maxValueForId + "))");
    LinkedHashSet<String> attrs = new LinkedHashSet<String>(1);
    attrs.add(Historical.HISTORICALATTRIBUTENAME);
    attrs.add(Historical.ENTRYUIDNAME);
    attrs.add("*");
    return conn.processSearch(
      new ASN1OctetString(baseDn.toString()),
      SearchScope.WHOLE_SUBTREE,
      DereferencePolicy.NEVER_DEREF_ALIASES,
      0, 0, false, filter,
      attrs,
      resultListener);
  }
  /**
   * {@inheritDoc}
   */
  public void handleInternalSearchEntry(
    InternalSearchOperation searchOperation,
    SearchResultEntry searchEntry)
  {
    /*
     * This call back is called at session establishment phase
     * for each entry that has been changed by this server and the changes
     * have not been sent to any Replication Server.
     * The role of this method is to build equivalent operation from
     * the historical information and add them in the replayOperations
     * table.
     */
    Iterable<FakeOperation> updates =
      Historical.generateFakeOperations(searchEntry);
    for (FakeOperation op : updates)
    {
      replayOperations.add(op);
    }
  }
  /**
   * {@inheritDoc}
   */
  public void handleInternalSearchReference(
    InternalSearchOperation searchOperation,
    SearchResultReference searchReference)
  {
    // TODO to be implemented
  }
  /**
   * This method should return the total number of objects in the
   * replicated domain.
   * This count will be used for reporting.
   *
   * @return true if the server is connected, false if not.
   * @throws DirectoryException when needed.
   *
   * @return The number of objects in the replication domain.
   */
  public boolean isConnected()
  public long countEntries() throws DirectoryException
  {
    if (broker != null)
      return broker.isConnected();
    else
    Backend backend = retrievesBackend(baseDn);
    if (!backend.supportsLDIFExport())
    {
      Message message = ERR_INIT_EXPORT_NOT_SUPPORTED.get(
                          backend.getBackendID().toString());
      logError(message);
      throw new DirectoryException(ResultCode.OTHER, message);
    }
    return backend.numSubordinates(baseDn, true) + 1;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean processUpdate(UpdateMsg updateMsg)
  {
    if (updateMsg instanceof LDAPUpdateMsg)
    {
      LDAPUpdateMsg msg = (LDAPUpdateMsg) updateMsg;
      // put the UpdateMsg in the RemotePendingChanges list.
      remotePendingChanges.putRemoteUpdate(msg);
      // Put update message into the replay queue
      // (block until some place in the queue is available)
      UpdateToReplay updateToReplay = new UpdateToReplay(msg, this);
      updateToReplayQueue.offer(updateToReplay);
      return false;
    }
    // unknown message type, this should not happen, just ignore it.
    return true;
  }
  /**
   * Determine whether the connection to the replication server is encrypted.
   * @return true if the connection is encrypted, false otherwise.
   * Monitoring information for the LDAPReplicationDomain.
   *
   * @return Monitoring attributes specific to the LDAPReplicationDomain.
   */
  public boolean isSessionEncrypted()
  public Collection<Attribute> getAdditionalMonitoring()
  {
    if (broker != null)
      return broker.isSessionEncrypted();
    else
      return false;
  }
    ArrayList<Attribute> attributes = new ArrayList<Attribute>();
  /**
   * Gets the info for DSs in the topology (except us).
   * @return The info for DSs in the topology (except us)
   */
  public List<DSInfo> getDsList()
  {
    return dsList;
  }
    /* get number of changes in the pending list */
    ReplicationMonitor.addMonitorData(
        attributes, "pending-updates", getPendingUpdatesCount());
  /**
   * Gets the info for RSs in the topology (except the one we are connected
   * to).
   * @return The info for RSs in the topology (except the one we are connected
   * to)
   */
  public List<RSInfo> getRsList()
  {
    return rsList;
  }
    /* get number of changes successfully */
    ReplicationMonitor.addMonitorData(attributes, "replayed-updates-ok",
        getNumReplayedPostOpCalled());
  /**
   * Tells if assured replication is enabled for this domain.
   * @return True if assured replication is enabled for this domain.
   */
  public boolean isAssured()
  {
    return assured;
  }
    /* get number of modify conflicts */
    ReplicationMonitor.addMonitorData(attributes, "resolved-modify-conflicts",
        getNumResolvedModifyConflicts());
  /**
   * Gives the mode for the assured replication of the domain.
   * @return The mode for the assured replication of the domain.
   */
  public AssuredMode getAssuredMode()
  {
    return assuredMode;
  }
    /* get number of naming conflicts */
    ReplicationMonitor.addMonitorData(attributes, "resolved-naming-conflicts",
        getNumResolvedNamingConflicts());
  /**
   * Gives the assured level of the replication of the domain.
   * @return The assured level of the replication of the domain.
   */
  public byte getAssuredSdLevel()
  {
    return assuredSdLevel;
  }
    /* get number of unresolved naming conflicts */
    ReplicationMonitor.addMonitorData(attributes, "unresolved-naming-conflicts",
        getNumUnresolvedNamingConflicts());
  /**
   * Gets the group id for this domain.
   * @return The group id for this domain.
   */
  public byte getGroupId()
  {
    return groupId;
  }
  /**
   * Gets the referrals URLs this domain publishes.
   * @return The referrals URLs this domain publishes.
   */
  public List<String> getRefUrls()
  {
    return refUrls;
  }
  /**
   * Gets the status for this domain.
   * @return The status for this domain.
   */
  public ServerStatus getStatus()
  {
    return status;
    return attributes;
  }
}
opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
@@ -91,8 +91,8 @@
                  ExportTaskListener
{
  private ReplicationServerListener replicationServerListener = null;
  private static Map<DN, ReplicationDomain> domains =
    new HashMap<DN, ReplicationDomain>() ;
  private static Map<DN, LDAPReplicationDomain> domains =
    new HashMap<DN, LDAPReplicationDomain>() ;
  /**
   * The queue of received update messages, to be treated by the ReplayThread
@@ -122,7 +122,8 @@
   *                   Can be null is the request has no associated operation.
   * @return           The domain for this DN.
   */
  public static ReplicationDomain findDomain(DN dn, PluginOperation pluginOp)
  public static LDAPReplicationDomain findDomain(
      DN dn, PluginOperation pluginOp)
  {
    /*
     * Don't run the special replication code on Operation that are
@@ -163,7 +164,7 @@
    }
    ReplicationDomain domain = null;
    LDAPReplicationDomain domain = null;
    DN temp = dn;
    do
    {
@@ -186,12 +187,12 @@
   * @return The domain created.
   * @throws ConfigException When the configuration is not valid.
   */
  public static ReplicationDomain createNewDomain(
  public static LDAPReplicationDomain createNewDomain(
      ReplicationDomainCfg configuration)
      throws ConfigException
  {
    ReplicationDomain domain;
    domain = new ReplicationDomain(configuration, updateToReplayQueue);
    LDAPReplicationDomain domain;
    domain = new LDAPReplicationDomain(configuration, updateToReplayQueue);
    if (domains.size() == 0)
    {
@@ -211,7 +212,7 @@
   */
  public static void deleteDomain(DN dn)
  {
    ReplicationDomain domain = domains.remove(dn);
    LDAPReplicationDomain domain = domains.remove(dn);
    if (domain != null)
      domain.shutdown();
@@ -310,7 +311,7 @@
  public boolean isConfigurationAddAcceptable(
      ReplicationDomainCfg configuration, List<Message> unacceptableReasons)
  {
    return ReplicationDomain.isConfigurationAcceptable(
    return LDAPReplicationDomain.isConfigurationAcceptable(
      configuration, unacceptableReasons);
  }
@@ -322,7 +323,7 @@
  {
    try
    {
      ReplicationDomain rd = createNewDomain(configuration);
      LDAPReplicationDomain rd = createNewDomain(configuration);
      if (isRegistered)
      {
        rd.start();
@@ -384,7 +385,7 @@
  public SynchronizationProviderResult handleConflictResolution(
      PreOperationModifyOperation modifyOperation)
  {
    ReplicationDomain domain =
    LDAPReplicationDomain domain =
      findDomain(modifyOperation.getEntryDN(), modifyOperation);
    if (domain == null)
      return new SynchronizationProviderResult.ContinueProcessing();
@@ -399,7 +400,7 @@
  public SynchronizationProviderResult handleConflictResolution(
      PreOperationAddOperation addOperation) throws DirectoryException
  {
    ReplicationDomain domain =
    LDAPReplicationDomain domain =
      findDomain(addOperation.getEntryDN(), addOperation);
    if (domain == null)
      return new SynchronizationProviderResult.ContinueProcessing();
@@ -414,7 +415,7 @@
  public SynchronizationProviderResult handleConflictResolution(
      PreOperationDeleteOperation deleteOperation) throws DirectoryException
  {
    ReplicationDomain domain =
    LDAPReplicationDomain domain =
      findDomain(deleteOperation.getEntryDN(), deleteOperation);
    if (domain == null)
      return new SynchronizationProviderResult.ContinueProcessing();
@@ -429,7 +430,7 @@
  public SynchronizationProviderResult handleConflictResolution(
      PreOperationModifyDNOperation modifyDNOperation) throws DirectoryException
  {
    ReplicationDomain domain =
    LDAPReplicationDomain domain =
      findDomain(modifyDNOperation.getEntryDN(), modifyDNOperation);
    if (domain == null)
      return new SynchronizationProviderResult.ContinueProcessing();
@@ -445,7 +446,7 @@
         doPreOperation(PreOperationModifyOperation modifyOperation)
  {
    DN operationDN = modifyOperation.getEntryDN();
    ReplicationDomain domain = findDomain(operationDN, modifyOperation);
    LDAPReplicationDomain domain = findDomain(operationDN, modifyOperation);
    if ((domain == null) || (!domain.solveConflict()))
      return new SynchronizationProviderResult.ContinueProcessing();
@@ -485,7 +486,7 @@
         throws DirectoryException
  {
    DN operationDN = modifyDNOperation.getEntryDN();
    ReplicationDomain domain = findDomain(operationDN, modifyDNOperation);
    LDAPReplicationDomain domain = findDomain(operationDN, modifyDNOperation);
    if ((domain == null) || (!domain.solveConflict()))
      return new SynchronizationProviderResult.ContinueProcessing();
@@ -513,7 +514,7 @@
  public SynchronizationProviderResult doPreOperation(
         PreOperationAddOperation addOperation)
  {
    ReplicationDomain domain =
    LDAPReplicationDomain domain =
      findDomain(addOperation.getEntryDN(), addOperation);
    if (domain == null)
      return new SynchronizationProviderResult.ContinueProcessing();
@@ -534,7 +535,7 @@
    isRegistered = false;
    // shutdown all the domains
    for (ReplicationDomain domain : domains.values())
    for (LDAPReplicationDomain domain : domains.values())
    {
      domain.shutdown();
    }
@@ -566,7 +567,7 @@
  @Override
  public void processSchemaChange(List<Modification> modifications)
  {
    ReplicationDomain domain =
    LDAPReplicationDomain domain =
      findDomain(DirectoryServer.getSchemaDN(), null);
    if (domain != null)
      domain.synchronizeModifications(modifications);
@@ -579,7 +580,7 @@
  {
    for (DN dn : backend.getBaseDNs())
    {
      ReplicationDomain domain = findDomain(dn, null);
      LDAPReplicationDomain domain = findDomain(dn, null);
      if (domain != null)
        domain.backupStart();
    }
@@ -593,7 +594,7 @@
  {
    for (DN dn : backend.getBaseDNs())
    {
      ReplicationDomain domain = findDomain(dn, null);
      LDAPReplicationDomain domain = findDomain(dn, null);
      if (domain != null)
        domain.backupEnd();
    }
@@ -606,7 +607,7 @@
  {
    for (DN dn : backend.getBaseDNs())
    {
      ReplicationDomain domain = findDomain(dn, null);
      LDAPReplicationDomain domain = findDomain(dn, null);
      if (domain != null)
        domain.disable();
    }
@@ -620,7 +621,7 @@
  {
    for (DN dn : backend.getBaseDNs())
    {
      ReplicationDomain domain = findDomain(dn, null);
      LDAPReplicationDomain domain = findDomain(dn, null);
      if (domain != null)
        domain.enable();
    }
@@ -633,7 +634,7 @@
  {
    for (DN dn : backend.getBaseDNs())
    {
      ReplicationDomain domain = findDomain(dn, null);
      LDAPReplicationDomain domain = findDomain(dn, null);
      if (domain != null)
        domain.disable();
    }
@@ -647,7 +648,7 @@
  {
    for (DN dn : backend.getBaseDNs())
    {
      ReplicationDomain domain = findDomain(dn, null);
      LDAPReplicationDomain domain = findDomain(dn, null);
      if (domain != null)
        domain.enable();
    }
@@ -660,7 +661,7 @@
  {
    for (DN dn : backend.getBaseDNs())
    {
      ReplicationDomain domain = findDomain(dn, null);
      LDAPReplicationDomain domain = findDomain(dn, null);
      if (domain != null)
        domain.backupStart();
    }
@@ -674,7 +675,7 @@
  {
    for (DN dn : backend.getBaseDNs())
    {
      ReplicationDomain domain = findDomain(dn, null);
      LDAPReplicationDomain domain = findDomain(dn, null);
      if (domain != null)
        domain.backupEnd();
    }
@@ -708,7 +709,7 @@
   */
  private void genericPostOperation(PostOperationOperation operation, DN dn)
  {
    ReplicationDomain domain = findDomain(dn, operation);
    LDAPReplicationDomain domain = findDomain(dn, operation);
    if (domain == null)
      return;
@@ -766,7 +767,7 @@
    isRegistered = true;
    // start all the domains
    for (ReplicationDomain domain : domains.values())
    for (LDAPReplicationDomain domain : domains.values())
    {
      domain.start();
    }
opends/src/server/org/opends/server/replication/plugin/PendingChange.java
@@ -28,7 +28,7 @@
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.replication.protocol.LDAPUpdateMsg;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.operation.PluginOperation;
@@ -41,7 +41,7 @@
{
  private ChangeNumber changeNumber;
  private boolean committed;
  private UpdateMsg msg;
  private LDAPUpdateMsg msg;
  private PluginOperation op;
  private ServerState dependencyState = null;
  private DN targetDN = null;
@@ -54,7 +54,7 @@
   */
  public PendingChange(ChangeNumber changeNumber,
                       PluginOperation op,
                       UpdateMsg msg)
                       LDAPUpdateMsg msg)
  {
    this.changeNumber = changeNumber;
    this.committed = false;
@@ -94,7 +94,7 @@
   * @return the message if operation was a replication operation
   * null if the operation was a local operation
   */
  public UpdateMsg getMsg()
  public LDAPUpdateMsg getMsg()
  {
    return msg;
  }
@@ -103,7 +103,7 @@
   * Set the message associated to the PendingChange.
   * @param msg the message
   */
  public void setMsg(UpdateMsg msg)
  public void setMsg(LDAPUpdateMsg msg)
  {
    this.msg = msg;
  }
opends/src/server/org/opends/server/replication/plugin/PendingChanges.java
@@ -26,14 +26,19 @@
 */
package org.opends.server.replication.plugin;
import static org.opends.messages.ReplicationMessages.*;
import static org.opends.server.loggers.ErrorLogger.logError;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeoutException;
import org.opends.messages.Message;
import org.opends.server.replication.service.ReplicationDomain;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.replication.protocol.LDAPUpdateMsg;
import org.opends.server.types.operation.PluginOperation;
/**
@@ -61,32 +66,23 @@
  private ChangeNumberGenerator changeNumberGenerator;
  /**
   * The Replicationbroker that will be used to send UpdateMsg.
   * The ReplicationDomain that will be used to send UpdateMsg.
   */
  private ReplicationBroker broker;
  /**
   * The ServerState that will be updated when UpdateMsg are committed.
   */
  private ServerState state;
  private ReplicationDomain domain;
  /**
   * Creates a new PendingChanges using the provided ChangeNumberGenerator.
   *
   * @param changeNumberGenerator The ChangeNumberGenerator to use to create
   *                               new unique ChangeNumbers.
   * @param broker  The Replicationbroker that will be used to send
   * @param domain  The ReplicationDomain that will be used to send
   *                UpdateMsg.
   * @param state   The ServerState that will be updated when UpdateMsg
   *                are committed.
   */
  public PendingChanges(
      ChangeNumberGenerator changeNumberGenerator, ReplicationBroker broker,
      ServerState state)
      ChangeNumberGenerator changeNumberGenerator, ReplicationDomain domain)
  {
    this.changeNumberGenerator = changeNumberGenerator;
    this.broker = broker;
    this.state = state;
    this.domain = domain;
  }
  /**
@@ -96,7 +92,7 @@
   *
   * @return The UpdateMsg that was just removed.
   */
  public synchronized UpdateMsg remove(ChangeNumber changeNumber)
  public synchronized LDAPUpdateMsg remove(ChangeNumber changeNumber)
  {
    return pendingChanges.remove(changeNumber).getMsg();
  }
@@ -119,7 +115,7 @@
   * @param msg          The message associated to the update.
   */
  public synchronized void commit(ChangeNumber changeNumber,
      UpdateMsg msg)
      LDAPUpdateMsg msg)
  {
    PendingChange curChange = pendingChanges.get(changeNumber);
    if (curChange == null)
@@ -186,9 +182,19 @@
          (firstChange.getOp().isSynchronizationOperation() == false))
      {
        numSentUpdates++;
        broker.publish(firstChange.getMsg());
      }
      state.update(firstChangeNumber);
        LDAPUpdateMsg updateMsg = firstChange.getMsg();
          try
          {
            domain.publish(updateMsg);
          } catch (TimeoutException ex) {
            // This exception may only be raised if assured replication is
            // enabled
            Message errorMsg = ERR_DS_ACK_TIMEOUT.get(
              domain.getServiceID(), Long.toString(domain.getAssuredTimeout()),
              updateMsg.toString());
            logError(errorMsg);
          }
        }
      pendingChanges.remove(firstChangeNumber);
      if (pendingChanges.isEmpty())
opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java
@@ -66,14 +66,15 @@
 * used to store the synchronized data and that is therefore persistent
 * across server reboot.
 */
public class PersistentServerState extends ServerState
public class PersistentServerState
{
   private DN baseDn;
   private boolean savedStatus = true;
   private InternalClientConnection conn =
   private final DN baseDn;
   private final InternalClientConnection conn =
       InternalClientConnection.getRootConnection();
   private ASN1OctetString asn1BaseDn;
   private short serverId;
   private final ASN1OctetString asn1BaseDn;
   private final short serverId;
   private final ServerState state;
   /**
    * The attribute name used to store the state in the backend.
@@ -83,24 +84,45 @@
  /**
   * create a new ServerState.
   * @param baseDn The baseDN for which the ServerState is created
   *  @param serverId The serverId
   * @param serverId The serverId
   */
  public PersistentServerState(DN baseDn, short serverId)
  {
    this.baseDn = baseDn;
    this.serverId = serverId;
    this.state = new ServerState();
    asn1BaseDn = new ASN1OctetString(baseDn.toString());
    loadState();
  }
  /**
   * {@inheritDoc}
   * Create a new PersistenServerState based on an already existing ServerState.
   *
   * @param baseDn    The baseDN for which the ServerState is created.
   * @param serverId  The serverId.
   * @param state     The serverState.
   */
  @Override
  public PersistentServerState(DN baseDn, short serverId, ServerState state)
  {
    this.baseDn = baseDn;
    this.serverId = serverId;
    this.state = state;
    asn1BaseDn = new ASN1OctetString(baseDn.toString());
    loadState();
  }
  /**
   * Update the Server State with a ChangeNumber.
   * All operations with smaller CSN and the same serverID must be committed
   * before calling this method.
   *
   * @param changeNumber    The committed ChangeNumber.
   *
   * @return a boolean indicating if the update was meaningful.
   */
  public boolean update(ChangeNumber changeNumber)
  {
    savedStatus = false;
    return super.update(changeNumber);
    return state.update(changeNumber);
  }
  /**
@@ -108,14 +130,14 @@
   */
  public void save()
  {
    if (savedStatus)
    if (state.isSaved())
      return;
    savedStatus = true;
    state.setSaved(true);
    ResultCode resultCode = updateStateEntry();
    if (resultCode != ResultCode.SUCCESS)
    {
        savedStatus = false;
      state.setSaved(false);
    }
  }
@@ -310,7 +332,7 @@
   */
  private ResultCode runUpdateStateEntry(DN serverStateEntryDN)
  {
    ArrayList<ASN1OctetString> values = this.toASN1ArrayList();
    ArrayList<ASN1OctetString> values = state.toASN1ArrayList();
    LDAPAttribute attr =
      new LDAPAttribute(REPLICATION_STATE, values);
@@ -347,8 +369,8 @@
   */
  public void clearInMemory()
  {
    super.clear();
    this.savedStatus = false;
    state.clear();
    state.setSaved(false);
  }
  /**
@@ -384,13 +406,13 @@
    // maxCn stored in the serverState
    synchronized (this)
    {
      serverStateMaxCn = this.getMaxChangeNumber(serverId);
      serverStateMaxCn = state.getMaxChangeNumber(serverId);
      if (serverStateMaxCn == null)
        return;
      try {
        op = ReplicationBroker.searchForChangedEntries(baseDn,
        op = LDAPReplicationDomain.searchForChangedEntries(baseDn,
            serverStateMaxCn, null);
      }
      catch (Exception  e)
@@ -448,4 +470,16 @@
      }
    }
  }
  /**
   * Get the largest ChangeNumber seen for a given LDAP server ID.
   *
   * @param serverID    The server ID.
   *
   * @return            The largest ChangeNumber seen.
   */
  public ChangeNumber getMaxChangeNumber(short serverID)
  {
    return state.getMaxChangeNumber(serverID);
  }
}
opends/src/server/org/opends/server/replication/plugin/RemotePendingChanges.java
@@ -37,13 +37,12 @@
import org.opends.server.core.ModifyDNOperationBasis;
import org.opends.server.core.ModifyOperation;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.replication.protocol.ModifyDNMsg;
import org.opends.server.replication.protocol.OperationContext;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.replication.protocol.LDAPUpdateMsg;
import org.opends.server.types.DN;
import org.opends.server.types.Operation;
@@ -76,42 +75,31 @@
    new TreeSet<PendingChange>();
  /**
   * The ServerState that will be updated when UpdateMsg are fully replayed.
   * The ServerState that will be updated when LDAPUpdateMsg are fully replayed.
   */
  private ServerState state;
  /**
   * The ChangeNumberGenerator to must be adjusted when new changes
   * are received from a remote server.
   */
  private ChangeNumberGenerator changeNumberGenerator;
  /**
   * Creates a new RemotePendingChanges using the provided ServerState.
   *
   * @param changeNumberGenerator The ChangeNumberGenerator that should
   *                              be adjusted when changes are received.
   * @param state   The ServerState that will be updated when UpdateMsg
   * @param state   The ServerState that will be updated when LDAPUpdateMsg
   *                have been fully replayed.
   */
  public RemotePendingChanges(ChangeNumberGenerator changeNumberGenerator,
                              ServerState state)
  public RemotePendingChanges(ServerState state)
  {
    this.changeNumberGenerator = changeNumberGenerator;
    this.state = state;
  }
  /**
   * Add a new UpdateMsg that was received from the replication server
   * Add a new LDAPUpdateMsg that was received from the replication server
   * to the pendingList.
   *
   * @param update The UpdateMsg that was received from the replication
   * @param update The LDAPUpdateMsg that was received from the replication
   *               server and that will be added to the pending list.
   */
  public synchronized void putRemoteUpdate(UpdateMsg update)
  public synchronized void putRemoteUpdate(LDAPUpdateMsg update)
  {
    ChangeNumber changeNumber = update.getChangeNumber();
    changeNumberGenerator.adjust(changeNumber);
    pendingChanges.put(changeNumber, new PendingChange(changeNumber, null,
                                                        update));
  }
@@ -154,9 +142,9 @@
  /**
   * Get the first update in the list that have some dependencies cleared.
   *
   * @return The UpdateMsg to be handled.
   * @return The LDAPUpdateMsg to be handled.
   */
  public synchronized UpdateMsg getNextUpdate()
  public synchronized LDAPUpdateMsg getNextUpdate()
  {
    /*
     * Parse the list of Update with dependencies and check if the dependencies
@@ -216,7 +204,7 @@
    {
      if (pendingChange.getChangeNumber().older(changeNumber))
      {
        UpdateMsg pendingMsg = pendingChange.getMsg();
        LDAPUpdateMsg pendingMsg = pendingChange.getMsg();
        if (pendingMsg != null)
        {
          if (pendingMsg instanceof DeleteMsg)
@@ -297,7 +285,7 @@
    {
      if (pendingChange.getChangeNumber().older(changeNumber))
      {
        UpdateMsg pendingMsg = pendingChange.getMsg();
        LDAPUpdateMsg pendingMsg = pendingChange.getMsg();
        if (pendingMsg != null)
        {
          if (pendingMsg instanceof AddMsg)
@@ -349,7 +337,7 @@
    {
      if (pendingChange.getChangeNumber().older(changeNumber))
      {
        UpdateMsg pendingMsg = pendingChange.getMsg();
        LDAPUpdateMsg pendingMsg = pendingChange.getMsg();
        if (pendingMsg != null)
        {
          if (pendingMsg instanceof DeleteMsg)
@@ -424,7 +412,7 @@
    {
      if (pendingChange.getChangeNumber().older(changeNumber))
      {
        UpdateMsg pendingMsg = pendingChange.getMsg();
        LDAPUpdateMsg pendingMsg = pendingChange.getMsg();
        if (pendingMsg != null)
        {
          if (pendingMsg instanceof DeleteMsg)
@@ -479,7 +467,7 @@
   * @return     A boolean indicating if an operation cannot be replayed
   *             because of dependencies.
   */
  public boolean checkDependencies(Operation op, UpdateMsg msg)
  public boolean checkDependencies(Operation op, LDAPUpdateMsg msg)
  {
    if (op instanceof ModifyOperation)
    {
opends/src/server/org/opends/server/replication/plugin/ReplLDIFOutputStream.java
@@ -30,6 +30,7 @@
import java.io.IOException;
import java.io.OutputStream;
import org.opends.server.replication.service.ReplicationDomain;
import org.opends.server.util.ServerConstants;
/**
@@ -103,8 +104,7 @@
          // of entries to export.
          throw(new IOException());
        }
        if (numEntries<0)
          domain.exportLDIFEntry(entryBuffer);
        numExportedEntries++;
        entryBuffer = "";
opends/src/server/org/opends/server/replication/plugin/ReplayThread.java
@@ -25,6 +25,8 @@
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
import org.opends.server.replication.protocol.LDAPUpdateMsg;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.opends.messages.Message;
@@ -37,7 +39,6 @@
import org.opends.server.api.DirectoryThread;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.replication.protocol.UpdateMsg;
/**
 * Thread that is used to get message from the replication servers (stored
@@ -102,8 +103,8 @@
          TimeUnit.SECONDS)) != null))
        {
          // Find replication domain for that update message
          UpdateMsg updateMsg = updateToreplay.getUpdateMessage();
          ReplicationDomain domain = updateToreplay.getReplicationDomain();
          LDAPUpdateMsg updateMsg = updateToreplay.getUpdateMessage();
          LDAPReplicationDomain domain = updateToreplay.getReplicationDomain();
          domain.replay(updateMsg);
        }
      } catch (Exception e)
opends/src/server/org/opends/server/replication/plugin/ReplicationMonitor.java
File was deleted
opends/src/server/org/opends/server/replication/plugin/UpdateToReplay.java
@@ -26,7 +26,7 @@
 */
package org.opends.server.replication.plugin;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.replication.protocol.LDAPUpdateMsg;
/**
 * This is a bag class to hold an update to replay in the queue of updates to
@@ -36,8 +36,8 @@
 */
public class UpdateToReplay
{
  private UpdateMsg updateMessage = null;
  private ReplicationDomain replicationDomain = null;
  private LDAPUpdateMsg updateMessage = null;
  private LDAPReplicationDomain replicationDomain = null;
  /**
   * Construct the object associating the update message with the replication
@@ -46,8 +46,8 @@
   * @param replicationDomain The replication domain to use for replaying the
   * change from the update message
   */
  public UpdateToReplay(UpdateMsg updateMessage,
    ReplicationDomain replicationDomain)
  public UpdateToReplay(LDAPUpdateMsg updateMessage,
    LDAPReplicationDomain replicationDomain)
  {
    this.updateMessage = updateMessage;
    this.replicationDomain = replicationDomain;
@@ -57,7 +57,7 @@
   * Getter for update message.
   * @return The update message
   */
  public UpdateMsg getUpdateMessage()
  public LDAPUpdateMsg getUpdateMessage()
  {
    return updateMessage;
  }
@@ -66,7 +66,7 @@
   * Getter for replication domain.
   * @return The replication domain
   */
  public ReplicationDomain getReplicationDomain()
  public LDAPReplicationDomain getReplicationDomain()
  {
    return replicationDomain;
  }
opends/src/server/org/opends/server/replication/protocol/AckMsg.java
@@ -110,6 +110,42 @@
  }
  /**
   * Sets the timeout marker for this message.
   * @param hasTimeout True if some timeout occurred
   */
  public void setHasTimeout(boolean hasTimeout)
  {
    this.hasTimeout = hasTimeout;
  }
  /**
   * Sets the wrong status marker for this message.
   * @param hasWrongStatus True if some servers were in wrong status
   */
  public void setHasWrongStatus(boolean hasWrongStatus)
  {
    this.hasWrongStatus = hasWrongStatus;
  }
  /**
   * Sets the replay error marker for this message.
   * @param hasReplayError True if some servers had errors replaying the change
   */
  public void setHasReplayError(boolean hasReplayError)
  {
    this.hasReplayError = hasReplayError;
  }
  /**
   * Sets the list of failing servers for this message.
   * @param failedServers The list of failing servers for this message.
   */
  public void setFailedServers(List<Short> failedServers)
  {
    this.failedServers = failedServers;
  }
  /**
   * Creates a new AckMsg by decoding the provided byte array.
   *
   * @param in The byte array containing the encoded form of the AckMsg.
@@ -277,4 +313,35 @@
    return failedServers;
  }
  /**
   * Transforms the errors information of the ack into human readable string.
   * @return A human readable string for errors embedded in the message.
   */
  public String errorsToString()
  {
    String idList = null;
    if (failedServers.size() > 0)
    {
      idList = "[";
      int size = failedServers.size();
      for (int i=0 ; i<size ; i++) {
        idList += failedServers.get(i);
        if ( i != (size-1) )
          idList += ", ";
      }
      idList += "]";
    } else
    {
      idList="none";
    }
    String ackErrorStr = "hasTimeout: " + (hasTimeout ? "yes" : "no")  + ", " +
      "hasWrongStatus: " + (hasWrongStatus ? "yes" : "no")  + ", " +
      "hasReplayError: " + (hasReplayError ? "yes" : "no")  + ", " +
      " concerned server ids: " + idList;
    return ackErrorStr;
  }
}
opends/src/server/org/opends/server/replication/protocol/AddMsg.java
@@ -58,7 +58,7 @@
 * This class is used to exchange Add operation between LDAP servers
 * and replication servers.
 */
public class AddMsg extends UpdateMsg
public class AddMsg extends LDAPUpdateMsg
{
  private byte[] encodedAttributes;
  private String parentUniqueId;
opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java
@@ -41,7 +41,7 @@
/**
 * Object used when sending delete information to replication servers.
 */
public class DeleteMsg extends UpdateMsg
public class DeleteMsg extends LDAPUpdateMsg
{
  /**
   * Creates a new delete message.
opends/src/server/org/opends/server/replication/protocol/EntryMsg.java
@@ -43,14 +43,39 @@
  /**
   * Creates a new EntryMsg.
   *
   * @param sender      The sender of this message.
   * @param destination The destination of this message.
   * @param entryBytes  The bytes of the entry.
   */
  public EntryMsg(
      short sender,
      short destination,
      byte[] entryBytes)
  {
    super(sender, destination);
    this.entryByteArray = new byte[entryBytes.length];
    System.arraycopy(entryBytes, 0, this.entryByteArray, 0, entryBytes.length);
  }
  /**
   * Creates a new EntryMsg.
   *
   * @param sender The sender of this message.
   * @param destination The destination of this message.
   * @param entryBytes The bytes of the entry.
   * @param pos         The starting Position in the array.
   * @param length      Number of array elements to be copied.
   */
  public EntryMsg(short sender, short destination, byte[] entryBytes)
  public EntryMsg(
      short sender,
      short destination,
      byte[] entryBytes,
      int pos,
      int length)
  {
    super(sender, destination);
    this.entryByteArray = entryBytes.clone();
    this.entryByteArray = new byte[length];
    System.arraycopy(entryBytes, pos, this.entryByteArray, 0, length);
  }
  /**
opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java
File was renamed from opends/src/server/org/opends/server/replication/plugin/HeartbeatMonitor.java
@@ -25,7 +25,7 @@
 *      Copyright 2007-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
package org.opends.server.replication.protocol;
import static org.opends.messages.ReplicationMessages.*;
import static org.opends.server.loggers.ErrorLogger.logError;
@@ -37,7 +37,6 @@
import java.io.IOException;
import org.opends.server.api.DirectoryThread;
import org.opends.server.replication.protocol.ProtocolSession;
/**
 * This class implements a thread to monitor heartbeat messages from the
opends/src/server/org/opends/server/replication/protocol/InitializeRequestMsg.java
@@ -49,10 +49,10 @@
   * @param destination destination of this message
   * @param senderID serverID of the server that will send this message
   */
  public InitializeRequestMsg(DN baseDn, short senderID, short destination)
  public InitializeRequestMsg(String baseDn, short senderID, short destination)
  {
    super(senderID, destination);
    this.baseDn = baseDn.toNormalizedString();
    this.baseDn = baseDn;
  }
  /**
opends/src/server/org/opends/server/replication/protocol/InitializeTargetMsg.java
@@ -29,9 +29,6 @@
import java.io.UnsupportedEncodingException;
import java.util.zip.DataFormatException;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
/**
 * This message is part of the replication protocol.
 * This message is sent by a server to one or several servers as the
@@ -59,12 +56,12 @@
   * @param requestorID The server that initiates this export.
   * @param entryCount The count of entries that will be sent.
   */
  public InitializeTargetMsg(DN baseDN, short senderID,
  public InitializeTargetMsg(String baseDN, short senderID,
      short destination, short requestorID, long entryCount)
  {
    super(senderID, destination);
    this.requestorID = requestorID;
    this.baseDN = baseDN.toNormalizedString();
    this.baseDN = baseDN;
    this.entryCount = entryCount;
  }
@@ -144,17 +141,9 @@
   *
   * @return the base DN
   */
  public DN getBaseDN()
  public String getBaseDN()
  {
    if (baseDN == null)
      return null;
    try
    {
      return DN.decode(baseDN);
    } catch (DirectoryException e)
    {
      return null;
    }
    return baseDN;
  }
  /**
opends/src/server/org/opends/server/replication/protocol/LDAPUpdateMsg.java
New file
@@ -0,0 +1,492 @@
/*
 * 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-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.protocol;
import java.io.UnsupportedEncodingException;
import java.util.zip.DataFormatException;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.common.AssuredMode;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.types.AbstractOperation;
import org.opends.server.types.LDAPException;
import org.opends.server.types.operation.PostOperationAddOperation;
import org.opends.server.types.operation.PostOperationDeleteOperation;
import org.opends.server.types.operation.PostOperationModifyDNOperation;
import org.opends.server.types.operation.PostOperationModifyOperation;
import org.opends.server.types.operation.PostOperationOperation;
/**
 * Abstract class that must be extended to define a message
 * used for sending Updates between servers.
 */
public abstract class LDAPUpdateMsg extends UpdateMsg
{
  /**
   * The DN on which the update was originally done.
   */
  protected String dn = null;
  /**
   * The uniqueId of the entry that was updated.
   */
  protected String uniqueId;
  /**
   * Creates a new UpdateMsg.
   */
  public LDAPUpdateMsg()
  {
  }
  /**
   * Creates a new UpdateMsg with the given informations.
   *
   * @param ctx The replication Context of the operation for which the
   *            update message must be created,.
   * @param dn The DN of the entry on which the change
   *           that caused the creation of this object happened
   */
  public LDAPUpdateMsg(OperationContext ctx, String dn)
  {
    this.protocolVersion = ProtocolVersion.getCurrentVersion();
    this.changeNumber = ctx.getChangeNumber();
    this.uniqueId = ctx.getEntryUid();
    this.dn = dn;
  }
  /**
   * Creates a new UpdateMessage with the given informations.
   *
   * @param cn        The ChangeNumber of the operation for which the
   *                  UpdateMessage is created.
   * @param entryUUID The Unique identifier of the entry that is updated
   *                  by the operation for which the UpdateMessage is created.
   * @param dn        The DN of the entry on which the change
   *                  that caused the creation of this object happened
   */
  public LDAPUpdateMsg(ChangeNumber cn, String entryUUID, String dn)
  {
    this.protocolVersion = ProtocolVersion.getCurrentVersion();
    this.changeNumber = cn;
    this.uniqueId = entryUUID;
    this.dn = dn;
  }
  /**
   * Generates an Update Message with the provided information.
   *
   * @param op The operation for which the message must be created.
   * @return The generated message.
   */
  public static LDAPUpdateMsg generateMsg(PostOperationOperation op)
  {
    LDAPUpdateMsg msg = null;
    switch (op.getOperationType())
    {
    case MODIFY :
      msg = new ModifyMsg((PostOperationModifyOperation) op);
      break;
    case ADD:
      msg = new AddMsg((PostOperationAddOperation) op);
      break;
    case DELETE :
      msg = new DeleteMsg((PostOperationDeleteOperation) op);
      break;
    case MODIFY_DN :
      msg = new ModifyDNMsg( (PostOperationModifyDNOperation) op);
      break;
    }
    return msg;
  }
  /**
   * Get the DN on which the operation happened.
   *
   * @return The DN on which the operations happened.
   */
  public String getDn()
  {
    return dn;
  }
  /**
   * Set the DN.
   * @param dn The dn that must now be used for this message.
   */
  public void setDn(String dn)
  {
    this.dn = dn;
  }
  /**
   * Get the Unique Identifier of the entry on which the operation happened.
   *
   * @return The Unique Identifier of the entry on which the operation happened.
   */
  public String getUniqueId()
  {
    return uniqueId;
  }
  /**
   * Create and Operation from the message.
   *
   * @param   conn connection to use when creating the message
   * @return  the created Operation
   * @throws  LDAPException In case of LDAP decoding exception.
   * @throws  ASN1Exception In case of ASN1 decoding exception.
   * @throws DataFormatException In case of bad msg format.
   */
  public AbstractOperation createOperation(InternalClientConnection conn)
         throws LDAPException, ASN1Exception, DataFormatException
  {
    return createOperation(conn, dn);
  }
  /**
   * Create and Operation from the message using the provided DN.
   *
   * @param   conn connection to use when creating the message.
   * @param   newDn the DN to use when creating the operation.
   * @return  the created Operation.
   * @throws  LDAPException In case of LDAP decoding exception.
   * @throws  ASN1Exception In case of ASN1 decoding exception.
   * @throws DataFormatException In case of bad msg format.
   */
  public abstract AbstractOperation createOperation(
         InternalClientConnection conn, String newDn)
         throws LDAPException, ASN1Exception, DataFormatException;
  /**
   * Encode the common header for all the UpdateMsg. This uses the current
   * protocol version.
   *
   * @param type the type of UpdateMsg to encode.
   * @param additionalLength additional length needed to encode the remaining
   *                         part of the UpdateMsg.
   * @return a byte array containing the common header and enough space to
   *         encode the remaining bytes of the UpdateMsg as was specified
   *         by the additionalLength.
   *         (byte array length = common header length + additionalLength)
   * @throws UnsupportedEncodingException if UTF-8 is not supported.
   */
  public byte[] encodeHeader(byte type, int additionalLength)
    throws UnsupportedEncodingException
  {
    byte[] byteDn = dn.getBytes("UTF-8");
    byte[] changeNumberByte =
      this.getChangeNumber().toString().getBytes("UTF-8");
    byte[] byteEntryuuid = getUniqueId().getBytes("UTF-8");
    /* The message header is stored in the form :
     * <operation type><protocol version><changenumber><dn><entryuuid><assured>
     * <assured mode> <safe data level>
     * the length of result byte array is therefore :
     *   1 + 1 + change number length + 1 + dn length + 1 + uuid length + 1 + 1
     *   + 1 + 1 + additional_length
     */
    int length = 8 + changeNumberByte.length + byteDn.length
                 + byteEntryuuid.length + additionalLength;
    byte[] encodedMsg = new byte[length];
    /* put the type of the operation */
    encodedMsg[0] = type;
    /* put the protocol version */
    encodedMsg[1] = (byte)ProtocolVersion.getCurrentVersion();
    int pos = 2;
    /* Put the ChangeNumber */
    pos = addByteArray(changeNumberByte, encodedMsg, pos);
    /* Put the DN and a terminating 0 */
    pos = addByteArray(byteDn, encodedMsg, pos);
    /* Put the entry uuid and a terminating 0 */
    pos = addByteArray(byteEntryuuid, encodedMsg, pos);
    /* Put the assured flag */
    encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0);
    /* Put the assured mode */
    encodedMsg[pos++] = assuredMode.getValue();
    /* Put the safe data level */
    encodedMsg[pos++] = safeDataLevel;
    return encodedMsg;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public byte[] getBytes(short reqProtocolVersion)
    throws UnsupportedEncodingException
  {
    // Using current protocol version should normally not be done as we would
    // normally call the getBytes() method instead for that. So this check
    // for security
    if (reqProtocolVersion == ProtocolVersion.getCurrentVersion())
    {
      return getBytes();
    }
    switch (reqProtocolVersion)
    {
      case ProtocolVersion.REPLICATION_PROTOCOL_V1:
        return getBytes_V1();
      default:
        // Unsupported requested version
        throw new UnsupportedEncodingException(getClass().getSimpleName() +
          " PDU does not support requested protocol version serialization: " +
          reqProtocolVersion);
    }
  }
  /**
   * Get the byte array representation of this Message. This uses the version
   * 1 of the replication protocol (used for compatibility purpose).
   *
   * @return The byte array representation of this Message.
   *
   * @throws UnsupportedEncodingException  When the encoding of the message
   *         failed because the UTF-8 encoding is not supported.
   */
  public abstract byte[] getBytes_V1() throws UnsupportedEncodingException;
  /**
   * Encode the common header for all the UpdateMessage. This uses the version
   * 1 of the replication protocol (used for compatibility purpose).
   *
   * @param type the type of UpdateMessage to encode.
   * @param additionalLength additional length needed to encode the remaining
   *                         part of the UpdateMessage.
   * @return a byte array containing the common header and enough space to
   *         encode the remaining bytes of the UpdateMessage as was specified
   *         by the additionalLength.
   *         (byte array length = common header length + additionalLength)
   * @throws UnsupportedEncodingException if UTF-8 is not supported.
   */
  public byte[] encodeHeader_V1(byte type, int additionalLength)
    throws UnsupportedEncodingException
  {
    byte[] byteDn = dn.getBytes("UTF-8");
    byte[] changeNumberByte =
      this.getChangeNumber().toString().getBytes("UTF-8");
    byte[] byteEntryuuid = getUniqueId().getBytes("UTF-8");
    /* The message header is stored in the form :
     * <operation type><changenumber><dn><assured><entryuuid><change>
     * the length of result byte array is therefore :
     *   1 + change number length + 1 + dn length + 1  + 1 +
     *   uuid length + 1 + additional_length
     */
    int length = 5 + changeNumberByte.length + byteDn.length
                 + byteEntryuuid.length + additionalLength;
    byte[] encodedMsg = new byte[length];
    /* put the type of the operation */
    encodedMsg[0] = type;
    int pos = 1;
    /* put the ChangeNumber */
    pos = addByteArray(changeNumberByte, encodedMsg, pos);
    /* put the assured information */
    encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0);
    /* put the DN and a terminating 0 */
    pos = addByteArray(byteDn, encodedMsg, pos);
    /* put the entry uuid and a terminating 0 */
    pos = addByteArray(byteEntryuuid, encodedMsg, pos);
    return encodedMsg;
  }
  /**
   * Decode the Header part of this Update Message, and check its type.
   *
   * @param types The allowed types of this Update Message.
   * @param encodedMsg the encoded form of the UpdateMsg.
   * @return the position at which the remaining part of the message starts.
   * @throws DataFormatException if the encodedMsg does not contain a valid
   *         common header.
   */
  public int decodeHeader(byte[] types, byte[] encodedMsg)
                          throws DataFormatException
  {
    /* The message header is stored in the form :
     * <operation type><protocol version><changenumber><dn><entryuuid><assured>
     * <assured mode> <safe data level>
     */
    /* first byte is the type */
    boolean foundMatchingType = false;
    for (int i = 0; i < types.length; i++)
    {
      if (types[i] == encodedMsg[0])
      {
        foundMatchingType = true;
        break;
      }
    }
    if (!foundMatchingType)
      throw new DataFormatException("byte[] is not a valid update msg: "
        + encodedMsg[0]);
    /*
     * For older protocol version PDUs, decode the matching version header
     * instead.
     */
    if ((encodedMsg[0] == MSG_TYPE_ADD_V1) ||
      (encodedMsg[0] == MSG_TYPE_DELETE_V1) ||
      (encodedMsg[0] == MSG_TYPE_MODIFYDN_V1) ||
      (encodedMsg[0] == MSG_TYPE_MODIFY_V1))
    {
      return decodeHeader_V1(encodedMsg);
    }
    /* read the protocol version */
    protocolVersion = (short)encodedMsg[1];
    try
    {
      /* Read the changeNumber */
      int pos = 2;
      int length = getNextLength(encodedMsg, pos);
      String changenumberStr = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      changeNumber = new ChangeNumber(changenumberStr);
      /* Read the dn */
      length = getNextLength(encodedMsg, pos);
      dn = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      /* Read the entryuuid */
      length = getNextLength(encodedMsg, pos);
      uniqueId = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      /* Read the assured information */
      if (encodedMsg[pos++] == 1)
        assuredFlag = true;
      else
        assuredFlag = false;
      /* Read the assured mode */
      assuredMode = AssuredMode.valueOf(encodedMsg[pos++]);
      /* Read the safe data level */
      safeDataLevel = encodedMsg[pos++];
      return pos;
    } catch (UnsupportedEncodingException e)
    {
      throw new DataFormatException("UTF-8 is not supported by this jvm.");
    } catch (IllegalArgumentException e)
    {
      throw new DataFormatException(e.getMessage());
    }
  }
  /**
   * Decode the Header part of this Update Message, and check its type. This
   * uses the version 1 of the replication protocol (used for compatibility
   * purpose).
   *
   * @param encodedMsg the encoded form of the UpdateMessage.
   * @return the position at which the remaining part of the message starts.
   * @throws DataFormatException if the encodedMsg does not contain a valid
   *         common header.
   */
  public int decodeHeader_V1(byte[] encodedMsg)
                          throws DataFormatException
  {
    if ((encodedMsg[0] != MSG_TYPE_ADD_V1) &&
      (encodedMsg[0] != MSG_TYPE_DELETE_V1) &&
      (encodedMsg[0] != MSG_TYPE_MODIFYDN_V1) &&
      (encodedMsg[0] != MSG_TYPE_MODIFY_V1))
      throw new DataFormatException("byte[] is not a valid update msg: expected"
        + " a V1 PDU, received: " + encodedMsg[0]);
    // Force version to V1 (other new parameters take their default values
    // (assured stuff...))
    protocolVersion = ProtocolVersion.REPLICATION_PROTOCOL_V1;
    try
    {
      /* read the changeNumber */
      int pos = 1;
      int length = getNextLength(encodedMsg, pos);
      String changenumberStr = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      changeNumber = new ChangeNumber(changenumberStr);
      /* read the assured information */
      if (encodedMsg[pos++] == 1)
        assuredFlag = true;
      else
        assuredFlag = false;
      /* read the dn */
      length = getNextLength(encodedMsg, pos);
      dn = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      /* read the entryuuid */
      length = getNextLength(encodedMsg, pos);
      uniqueId = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      return pos;
    } catch (UnsupportedEncodingException e)
    {
      throw new DataFormatException("UTF-8 is not supported by this jvm.");
    }
  }
  /**
   * Return the number of bytes used by this message.
   *
   * @return The number of bytes used by this message.
   */
  public abstract int size();
}
opends/src/server/org/opends/server/replication/protocol/ModifyCommonMsg.java
@@ -41,7 +41,7 @@
/**
 * This class holds every common code for the modify messages (mod, moddn).
 */
public abstract class ModifyCommonMsg extends UpdateMsg {
public abstract class ModifyCommonMsg extends LDAPUpdateMsg {
  /**
   * The modifications kept encoded in the message.
opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java
@@ -30,8 +30,6 @@
import java.util.zip.DataFormatException;
import org.opends.server.replication.common.ServerState;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
/**
 * Message sent by a replication server to another replication server
@@ -73,7 +71,7 @@
   * @param groupId The group id of the RS
   * @param degradedStatusThreshold The degraded status threshold
   */
  public ReplServerStartMsg(short serverId, String serverURL, DN baseDn,
  public ReplServerStartMsg(short serverId, String serverURL, String baseDn,
                               int windowSize,
                               ServerState serverState,
                               short protocolVersion,
@@ -86,7 +84,7 @@
    this.serverId = serverId;
    this.serverURL = serverURL;
    if (baseDn != null)
      this.baseDn = baseDn.toNormalizedString();
      this.baseDn = baseDn;
    else
      this.baseDn = null;
    this.windowSize = windowSize;
@@ -202,17 +200,9 @@
   *
   * @return the base DN from this ReplServerStartMsg.
   */
  public DN getBaseDn()
  public String getBaseDn()
  {
    if (baseDn == null)
      return null;
    try
    {
      return DN.decode(baseDn);
    } catch (DirectoryException e)
    {
      return null;
    }
    return baseDn;
  }
  /**
@@ -333,8 +323,8 @@
    return "ReplServerStartMsg content: " +
      "\nprotocolVersion: " + protocolVersion +
      "\ngenerationId: " + generationId +
      "\nbaseDn: " + baseDn +
      "\ngroupId: " + groupId +
      "\nbaseDn: " + baseDn.toString() +
      "\nserverId: " + serverId +
      "\nserverState: " + serverState +
      "\nserverURL: " + serverURL +
opends/src/server/org/opends/server/replication/protocol/ReplSessionSecurity.java
@@ -31,8 +31,6 @@
import static org.opends.messages.ReplicationMessages.*;
import org.opends.messages.Message;
import org.opends.server.admin.std.server.ReplicationServerCfg;
import org.opends.server.admin.std.server.ReplicationDomainCfg;
import org.opends.server.types.DirectoryConfig;
import org.opends.server.types.CryptoManager;
import org.opends.server.config.ConfigException;
@@ -132,32 +130,12 @@
  }
  /**
   * Create a ReplSessionSecurity instance from a provided replication server
   * configuration.
   *
   * @param replServerCfg The replication server configuration.
   *
   * @throws ConfigException If the supplied configuration was not valid.
   */
  public ReplSessionSecurity(ReplicationServerCfg replServerCfg)
    throws ConfigException
  {
    // Currently use global settings from the crypto manager.
    this(DirectoryConfig.getCryptoManager().getSslCertNickname(),
      DirectoryConfig.getCryptoManager().getSslProtocols(),
      DirectoryConfig.getCryptoManager().getSslCipherSuites(),
      DirectoryConfig.getCryptoManager().isSslEncryption());
  }
  /**
   * Create a ReplSessionSecurity instance from a provided multimaster domain
   * configuration.
   *
   * @param multimasterDomainCfg The multimaster domain configuration.
   *
   * @throws ConfigException If the supplied configuration was not valid.
   */
  public ReplSessionSecurity(ReplicationDomainCfg multimasterDomainCfg)
  public ReplSessionSecurity()
    throws ConfigException
  {
    // Currently use global settings from the crypto manager.
opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java
@@ -69,6 +69,7 @@
  static final byte MSG_TYPE_TOPOLOGY = 26;
  static final byte MSG_TYPE_START_SESSION = 27;
  static final byte MSG_TYPE_CHANGE_STATUS = 28;
  static final byte MSG_TYPE_GENERIC_UPDATE = 29;
  // Adding a new type of message here probably requires to
  // change accordingly generateMsg method below
@@ -209,6 +210,9 @@
      case MSG_TYPE_CHANGE_STATUS:
        msg = new ChangeStatusMsg(buffer);
      break;
      case MSG_TYPE_GENERIC_UPDATE:
        msg = new UpdateMsg(buffer);
      break;
      default:
        throw new DataFormatException("received message with unknown type");
    }
@@ -224,7 +228,8 @@
   * @param pos the position where to concatenate.
   * @return the next position to use in the resultByteArray.
   */
  protected int addByteArray(byte[] tail, byte[] resultByteArray, int pos)
  protected static int addByteArray(byte[] tail, byte[] resultByteArray,
    int pos)
  {
    for (int i=0; i<tail.length; i++,pos++)
    {
opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java
@@ -32,8 +32,6 @@
import java.util.zip.DataFormatException;
import org.opends.server.replication.common.ServerState;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
/**
 * This message is used by LDAP server when they first connect.
@@ -85,7 +83,7 @@
   *                      after the start messages have been exchanged.
   * @param groupId The group id of the DS for this DN
   */
  public ServerStartMsg(short serverId, DN baseDn, int maxReceiveDelay,
  public ServerStartMsg(short serverId, String baseDn, int maxReceiveDelay,
                            int maxReceiveQueue, int maxSendDelay,
                            int maxSendQueue, int windowSize,
                            long heartbeatInterval,
@@ -98,7 +96,7 @@
    super(protocolVersion, generationId);
    this.serverId = serverId;
    this.baseDn = baseDn.toString();
    this.baseDn = baseDn;
    this.maxReceiveDelay = maxReceiveDelay;
    this.maxReceiveQueue = maxReceiveQueue;
    this.maxSendDelay = maxSendDelay;
@@ -243,15 +241,9 @@
   * Get the baseDn.
   * @return Returns the baseDn.
   */
  public DN getBaseDn()
  public String getBaseDn()
  {
    try
    {
      return DN.decode(baseDn);
    } catch (DirectoryException e)
    {
      return null;
    }
    return baseDn;
  }
  /**
opends/src/server/org/opends/server/replication/protocol/UpdateMsg.java
@@ -26,26 +26,17 @@
 */
package org.opends.server.replication.protocol;
import java.io.UnsupportedEncodingException;
import java.util.zip.DataFormatException;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.internal.InternalClientConnection;
import java.io.UnsupportedEncodingException;
import org.opends.server.replication.common.AssuredMode;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.types.AbstractOperation;
import org.opends.server.types.LDAPException;
import org.opends.server.types.operation.PostOperationAddOperation;
import org.opends.server.types.operation.PostOperationDeleteOperation;
import org.opends.server.types.operation.PostOperationModifyDNOperation;
import org.opends.server.types.operation.PostOperationModifyOperation;
import org.opends.server.types.operation.PostOperationOperation;
/**
 * Abstract class that must be extended to define a message
 * used for sending Updates between servers.
 */
public abstract class UpdateMsg extends ReplicationMsg
public class UpdateMsg extends ReplicationMsg
                                    implements Comparable<UpdateMsg>
{
  /**
@@ -59,11 +50,6 @@
  protected ChangeNumber changeNumber;
  /**
   * The DN on which the update was originally done.
   */
  protected String dn = null;
  /**
   * True when the update must use assured replication.
   */
  protected boolean assuredFlag = false;
@@ -76,83 +62,62 @@
  /**
   * When assured mode is safe data, gives the requested level.
   */
  protected byte safeDataLevel = (byte)-1;
  protected byte safeDataLevel = (byte)1;
  /**
   * The uniqueId of the entry that was updated.
   * The payload that must be encoded in this message.
   */
  protected String uniqueId;
  private byte[] payload;
  /**
   * Creates a new UpdateMsg.
   * Creates a new empty UpdateMsg.
   */
  public UpdateMsg()
  protected UpdateMsg()
  {}
  /**
   * Creates a new UpdateMsg with the given informations.
   *
   * @param bytes A Byte Array with the encoded form of the message.
   *
   * @throws DataFormatException If bytes is not valid.
   */
  UpdateMsg(byte[] bytes) throws DataFormatException
  {
    // Decode header
    int pos = decodeHeader(MSG_TYPE_GENERIC_UPDATE, bytes);
    /* Read the payload : all the remaining bytes but the terminating 0 */
    int length = bytes.length - pos;
    payload = new byte[length];
    try
    {
      System.arraycopy(bytes, pos, payload, 0, length);
    } catch (IndexOutOfBoundsException e)
    {
      throw new DataFormatException(e.getMessage());
    } catch (ArrayStoreException e)
    {
      throw new DataFormatException(e.getMessage());
    } catch (NullPointerException e)
    {
      throw new DataFormatException(e.getMessage());
    }
  }
  /**
   * Creates a new UpdateMsg with the given informations.
   *
   * @param ctx The replication Context of the operation for which the
   *            update message must be created,.
   * @param dn The DN of the entry on which the change
   *           that caused the creation of this object happened
   * @param changeNumber  The ChangeNumber associated with the change
   *                      encoded in this message.
   * @param payload       The payload that must be encoded in this message.
   */
  public UpdateMsg(OperationContext ctx, String dn)
  public UpdateMsg(ChangeNumber changeNumber, byte[] payload)
  {
    this.payload = payload;
    this.protocolVersion = ProtocolVersion.getCurrentVersion();
    this.changeNumber = ctx.getChangeNumber();
    this.uniqueId = ctx.getEntryUid();
    this.dn = dn;
  }
  /**
   * Creates a new UpdateMessage with the given informations.
   *
   * @param cn        The ChangeNumber of the operation for which the
   *                  UpdateMessage is created.
   * @param entryUUID The Unique identifier of the entry that is updated
   *                  by the operation for which the UpdateMessage is created.
   * @param dn        The DN of the entry on which the change
   *                  that caused the creation of this object happened
   */
  public UpdateMsg(ChangeNumber cn, String entryUUID, String dn)
  {
    this.protocolVersion = ProtocolVersion.getCurrentVersion();
    this.changeNumber = cn;
    this.uniqueId = entryUUID;
    this.dn = dn;
  }
  /**
   * Generates an Update Message with the provided information.
   *
   * @param op The operation for which the message must be created.
   * @return The generated message.
   */
  public static UpdateMsg generateMsg(PostOperationOperation op)
  {
    UpdateMsg msg = null;
    switch (op.getOperationType())
    {
    case MODIFY :
      msg = new ModifyMsg((PostOperationModifyOperation) op);
      break;
    case ADD:
      msg = new AddMsg((PostOperationAddOperation) op);
      break;
    case DELETE :
      msg = new DeleteMsg((PostOperationDeleteOperation) op);
      break;
    case MODIFY_DN :
      msg = new ModifyDNMsg( (PostOperationModifyDNOperation) op);
      break;
    }
    return msg;
    this.changeNumber = changeNumber;
  }
  /**
@@ -165,35 +130,6 @@
  }
  /**
   * Get the DN on which the operation happened.
   *
   * @return The DN on which the operations happened.
   */
  public String getDn()
  {
    return dn;
  }
  /**
   * Set the DN.
   * @param dn The dn that must now be used for this message.
   */
  public void setDn(String dn)
  {
    this.dn = dn;
  }
  /**
   * Get the Unique Identifier of the entry on which the operation happened.
   *
   * @return The Unique Identifier of the entry on which the operation happened.
   */
  public String getUniqueId()
  {
    return uniqueId;
  }
  /**
   * Get a boolean indicating if the Update must be processed as an
   * Asynchronous or as an assured replication.
   *
@@ -250,96 +186,6 @@
    return changeNumber.compareTo(msg.getChangeNumber());
  }
  /**
   * Create and Operation from the message.
   *
   * @param   conn connection to use when creating the message
   * @return  the created Operation
   * @throws  LDAPException In case of LDAP decoding exception.
   * @throws  ASN1Exception In case of ASN1 decoding exception.
   * @throws DataFormatException In case of bad msg format.
   */
  public AbstractOperation createOperation(InternalClientConnection conn)
         throws LDAPException, ASN1Exception, DataFormatException
  {
    return createOperation(conn, dn);
  }
  /**
   * Create and Operation from the message using the provided DN.
   *
   * @param   conn connection to use when creating the message.
   * @param   newDn the DN to use when creating the operation.
   * @return  the created Operation.
   * @throws  LDAPException In case of LDAP decoding exception.
   * @throws  ASN1Exception In case of ASN1 decoding exception.
   * @throws DataFormatException In case of bad msg format.
   */
  public abstract AbstractOperation createOperation(
         InternalClientConnection conn, String newDn)
         throws LDAPException, ASN1Exception, DataFormatException;
  /**
   * Encode the common header for all the UpdateMsg. This uses the current
   * protocol version.
   *
   * @param type the type of UpdateMsg to encode.
   * @param additionalLength additional length needed to encode the remaining
   *                         part of the UpdateMsg.
   * @return a byte array containing the common header and enough space to
   *         encode the remaining bytes of the UpdateMsg as was specified
   *         by the additionalLength.
   *         (byte array length = common header length + additionalLength)
   * @throws UnsupportedEncodingException if UTF-8 is not supported.
   */
  public byte[] encodeHeader(byte type, int additionalLength)
    throws UnsupportedEncodingException
  {
    byte[] byteDn = dn.getBytes("UTF-8");
    byte[] changeNumberByte =
      this.getChangeNumber().toString().getBytes("UTF-8");
    byte[] byteEntryuuid = getUniqueId().getBytes("UTF-8");
    /* The message header is stored in the form :
     * <operation type><protocol version><changenumber><dn><entryuuid><assured>
     * <assured mode> <safe data level>
     * the length of result byte array is therefore :
     *   1 + 1 + change number length + 1 + dn length + 1 + uuid length + 1 + 1
     *   + 1 + 1 + additional_length
     */
    int length = 8 + changeNumberByte.length + byteDn.length
                 + byteEntryuuid.length + additionalLength;
    byte[] encodedMsg = new byte[length];
    /* put the type of the operation */
    encodedMsg[0] = type;
    /* put the protocol version */
    encodedMsg[1] = (byte)ProtocolVersion.getCurrentVersion();
    int pos = 2;
    /* Put the ChangeNumber */
    pos = addByteArray(changeNumberByte, encodedMsg, pos);
    /* Put the DN and a terminating 0 */
    pos = addByteArray(byteDn, encodedMsg, pos);
    /* Put the entry uuid and a terminating 0 */
    pos = addByteArray(byteEntryuuid, encodedMsg, pos);
    /* Put the assured flag */
    encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0);
    /* Put the assured mode */
    encodedMsg[pos++] = assuredMode.getValue();
    /* Put the safe data level */
    encodedMsg[pos++] = safeDataLevel;
    return encodedMsg;
  }
  /**
   * {@inheritDoc}
@@ -353,226 +199,15 @@
    {
      return getBytes();
    }
    // Supported older protocol versions
    switch (reqProtocolVersion)
    else
    {
      case ProtocolVersion.REPLICATION_PROTOCOL_V1:
        return getBytes_V1();
      default:
        // Unsupported requested version
        throw new UnsupportedEncodingException(getClass().getSimpleName() +
      throw new UnsupportedEncodingException(getClass().getSimpleName() +
          " PDU does not support requested protocol version serialization: " +
          reqProtocolVersion);
    }
  }
  /**
   * Get the byte array representation of this Message. This uses the version
   * 1 of the replication protocol (used for compatibility purpose).
   *
   * @return The byte array representation of this Message.
   *
   * @throws UnsupportedEncodingException  When the encoding of the message
   *         failed because the UTF-8 encoding is not supported.
   */
  public abstract byte[] getBytes_V1() throws UnsupportedEncodingException;
  /**
   * Encode the common header for all the UpdateMessage. This uses the version
   * 1 of the replication protocol (used for compatibility purpose).
   *
   * @param type the type of UpdateMessage to encode.
   * @param additionalLength additional length needed to encode the remaining
   *                         part of the UpdateMessage.
   * @return a byte array containing the common header and enough space to
   *         encode the remaining bytes of the UpdateMessage as was specified
   *         by the additionalLength.
   *         (byte array length = common header length + additionalLength)
   * @throws UnsupportedEncodingException if UTF-8 is not supported.
   */
  public byte[] encodeHeader_V1(byte type, int additionalLength)
    throws UnsupportedEncodingException
  {
    byte[] byteDn = dn.getBytes("UTF-8");
    byte[] changeNumberByte =
      this.getChangeNumber().toString().getBytes("UTF-8");
    byte[] byteEntryuuid = getUniqueId().getBytes("UTF-8");
    /* The message header is stored in the form :
     * <operation type>changenumber><dn><assured><entryuuid><change>
     * the length of result byte array is therefore :
     *   1 + change number length + 1 + dn length + 1  + 1 +
     *   uuid length + 1 + additional_length
     */
    int length = 5 + changeNumberByte.length + byteDn.length
                 + byteEntryuuid.length + additionalLength;
    byte[] encodedMsg = new byte[length];
    /* put the type of the operation */
    encodedMsg[0] = type;
    int pos = 1;
    /* put the ChangeNumber */
    pos = addByteArray(changeNumberByte, encodedMsg, pos);
    /* put the assured information */
    encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0);
    /* put the DN and a terminating 0 */
    pos = addByteArray(byteDn, encodedMsg, pos);
    /* put the entry uuid and a terminating 0 */
    pos = addByteArray(byteEntryuuid, encodedMsg, pos);
    return encodedMsg;
  }
  /**
   * Decode the Header part of this Update Message, and check its type.
   *
   * @param types The allowed types of this Update Message.
   * @param encodedMsg the encoded form of the UpdateMsg.
   * @return the position at which the remaining part of the message starts.
   * @throws DataFormatException if the encodedMsg does not contain a valid
   *         common header.
   */
  public int decodeHeader(byte[] types, byte[] encodedMsg)
                          throws DataFormatException
  {
    /* The message header is stored in the form :
     * <operation type><protocol version><changenumber><dn><entryuuid><assured>
     * <assured mode> <safe data level>
     */
    /* first byte is the type */
    boolean foundMatchingType = false;
    for (int i = 0; i < types.length; i++)
    {
      if (types[i] == encodedMsg[0])
      {
        foundMatchingType = true;
        break;
      }
    }
    if (!foundMatchingType)
      throw new DataFormatException("byte[] is not a valid update msg: "
        + encodedMsg[0]);
    /*
     * For older protocol version PDUs, decode the matching version header
     * instead.
     */
    if ((encodedMsg[0] == MSG_TYPE_ADD_V1) ||
      (encodedMsg[0] == MSG_TYPE_DELETE_V1) ||
      (encodedMsg[0] == MSG_TYPE_MODIFYDN_V1) ||
      (encodedMsg[0] == MSG_TYPE_MODIFY_V1))
    {
      return decodeHeader_V1(encodedMsg);
    }
    /* read the protocol version */
    protocolVersion = (short)encodedMsg[1];
    try
    {
      /* Read the changeNumber */
      int pos = 2;
      int length = getNextLength(encodedMsg, pos);
      String changenumberStr = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      changeNumber = new ChangeNumber(changenumberStr);
      /* Read the dn */
      length = getNextLength(encodedMsg, pos);
      dn = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      /* Read the entryuuid */
      length = getNextLength(encodedMsg, pos);
      uniqueId = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      /* Read the assured information */
      if (encodedMsg[pos++] == 1)
        assuredFlag = true;
      else
        assuredFlag = false;
      /* Read the assured mode */
      assuredMode = AssuredMode.valueOf(encodedMsg[pos++]);
      /* Read the safe data level */
      safeDataLevel = encodedMsg[pos++];
      return pos;
    } catch (UnsupportedEncodingException e)
    {
      throw new DataFormatException("UTF-8 is not supported by this jvm.");
    } catch (IllegalArgumentException e)
    {
      throw new DataFormatException(e.getMessage());
    }
  }
  /**
   * Decode the Header part of this Update Message, and check its type. This
   * uses the version 1 of the replication protocol (used for compatibility
   * purpose).
   *
   * @param encodedMsg the encoded form of the UpdateMessage.
   * @return the position at which the remaining part of the message starts.
   * @throws DataFormatException if the encodedMsg does not contain a valid
   *         common header.
   */
  public int decodeHeader_V1(byte[] encodedMsg)
                          throws DataFormatException
  {
    if ((encodedMsg[0] != MSG_TYPE_ADD_V1) &&
      (encodedMsg[0] != MSG_TYPE_DELETE_V1) &&
      (encodedMsg[0] != MSG_TYPE_MODIFYDN_V1) &&
      (encodedMsg[0] != MSG_TYPE_MODIFY_V1))
      throw new DataFormatException("byte[] is not a valid update msg: expected"
        + " a V1 PDU, received: " + encodedMsg[0]);
    // Force version to V1 (other new parameters take their default values
    // (assured stuff...))
    protocolVersion = ProtocolVersion.REPLICATION_PROTOCOL_V1;
    try
    {
      /* read the changeNumber */
      int pos = 1;
      int length = getNextLength(encodedMsg, pos);
      String changenumberStr = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      changeNumber = new ChangeNumber(changenumberStr);
      /* read the assured information */
      if (encodedMsg[pos++] == 1)
        assuredFlag = true;
      else
        assuredFlag = false;
      /* read the dn */
      length = getNextLength(encodedMsg, pos);
      dn = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      /* read the entryuuid */
      length = getNextLength(encodedMsg, pos);
      uniqueId = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      return pos;
    } catch (UnsupportedEncodingException e)
    {
      throw new DataFormatException("UTF-8 is not supported by this jvm.");
    }
  }
  /**
   * Get the assured mode in this message.
   * @return The assured mode in this message
   */
@@ -626,5 +261,144 @@
   *
   * @return The number of bytes used by this message.
   */
  public abstract int size();
  public int size()
  {
    return 10 + payload.length;
  }
  /**
   * Encode the common header for all the UpdateMsg. This uses the current
   * protocol version.
   *
   * @param type the type of UpdateMsg to encode.
   * @param additionalLength additional length needed to encode the remaining
   *                         part of the UpdateMsg.
   * @return a byte array containing the common header and enough space to
   *         encode the remaining bytes of the UpdateMsg as was specified
   *         by the additionalLength.
   *         (byte array length = common header length + additionalLength)
   * @throws UnsupportedEncodingException if UTF-8 is not supported.
   */
  protected byte[] encodeHeader(byte type, int additionalLength)
    throws UnsupportedEncodingException
  {
    byte[] changeNumberByte =
      this.getChangeNumber().toString().getBytes("UTF-8");
    /* The message header is stored in the form :
     * <operation type><protocol version><changenumber><assured>
     * <assured mode> <safe data level>
     * the length of result byte array is therefore :
     *   1 + 1 + change number length + 1 + 1
     *   + 1 + 1 + additional_length
     */
    int length = 6 + changeNumberByte.length + additionalLength;
    byte[] encodedMsg = new byte[length];
    /* put the type of the operation */
    encodedMsg[0] = type;
    /* put the protocol version */
    encodedMsg[1] = (byte)ProtocolVersion.getCurrentVersion();
    int pos = 2;
    /* Put the ChangeNumber */
    pos = addByteArray(changeNumberByte, encodedMsg, pos);
    /* Put the assured flag */
    encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0);
    /* Put the assured mode */
    encodedMsg[pos++] = assuredMode.getValue();
    /* Put the safe data level */
    encodedMsg[pos++] = safeDataLevel;
    return encodedMsg;
  }
  /**
   * Decode the Header part of this Update Message, and check its type.
   *
   * @param type The allowed type of this Update Message.
   * @param encodedMsg the encoded form of the UpdateMsg.
   * @return the position at which the remaining part of the message starts.
   * @throws DataFormatException if the encodedMsg does not contain a valid
   *         common header.
   */
  protected int decodeHeader(byte type, byte[] encodedMsg)
                          throws DataFormatException
  {
    /* The message header is stored in the form :
     * <operation type><protocol version><changenumber><assured>
     * <assured mode> <safe data level>
     */
    if (!(type == encodedMsg[0]))
      throw new DataFormatException("byte[] is not a valid update msg: "
        + encodedMsg[0]);
    /* read the protocol version */
    protocolVersion = (short)encodedMsg[1];
    try
    {
      /* Read the changeNumber */
      int pos = 2;
      int length = getNextLength(encodedMsg, pos);
      String changenumberStr = new String(encodedMsg, pos, length, "UTF-8");
      pos += length + 1;
      changeNumber = new ChangeNumber(changenumberStr);
      /* Read the assured information */
      if (encodedMsg[pos++] == 1)
        assuredFlag = true;
      else
        assuredFlag = false;
      /* Read the assured mode */
      assuredMode = AssuredMode.valueOf(encodedMsg[pos++]);
      /* Read the safe data level */
      safeDataLevel = encodedMsg[pos++];
      return pos;
    } catch (UnsupportedEncodingException e)
    {
      throw new DataFormatException("UTF-8 is not supported by this jvm.");
    } catch (IllegalArgumentException e)
    {
      throw new DataFormatException(e.getMessage());
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public byte[] getBytes() throws UnsupportedEncodingException
  {
    /* Encode the header in a byte[] large enough to also contain the payload */
    byte [] resultByteArray =
      encodeHeader(MSG_TYPE_GENERIC_UPDATE, payload.length);
    int pos = resultByteArray.length - payload.length;
    /* Add the payload */
    for (int i=0; i<payload.length; i++,pos++)
    {
      resultByteArray[pos] = payload[i];
    }
    return resultByteArray;
  }
  /**
   * Get the payload of the UpdateMsg.
   *
   * @return The payload of the UpdateMsg.
   */
  public byte[] getPayload()
  {
    return payload;
  }
}
opends/src/server/org/opends/server/replication/protocol/WindowMsg.java
@@ -137,7 +137,6 @@
  @Override
  public String toString()
  {
    return "ServerStartMsg content: " +
      "\nnumAck: " + numAck;
    return "WindowMsg : " + "numAck: " + numAck;
  }
}
opends/src/server/org/opends/server/replication/server/AckMessageList.java
File was deleted
opends/src/server/org/opends/server/replication/server/DbHandler.java
@@ -43,7 +43,6 @@
import org.opends.server.config.ConfigException;
import org.opends.server.types.Attribute;
import org.opends.server.types.Attributes;
import org.opends.server.types.DN;
import org.opends.server.types.InitializationException;
import org.opends.server.util.TimeThread;
import org.opends.server.core.DirectoryServer;
@@ -102,7 +101,7 @@
  private ChangeNumber firstChange = null;
  private ChangeNumber lastChange = null;
  private short serverId;
  private DN baseDn;
  private String baseDn;
  private DbMonitorProvider dbMonitor = new DbMonitorProvider();
  private boolean shutdown = false;
  private boolean done = false;
@@ -133,7 +132,7 @@
   * @throws DatabaseException If a database problem happened
   */
  public DbHandler(
      short id, DN baseDn, ReplicationServer replicationServer,
      short id, String baseDn, ReplicationServer replicationServer,
      ReplicationDbEnv dbenv, int queueSize)
         throws DatabaseException
  {
opends/src/server/org/opends/server/replication/server/ExpectedAcksInfo.java
New file
@@ -0,0 +1,141 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.server;
import org.opends.server.replication.common.AssuredMode;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.protocol.AckMsg;
/**
 * This class is the mother class for sub-classes holding any information needed
 * about the acks that the replication server will wait for, when he receives an
 * update message with the assured flag on (assured replication acknowledgments
 * expected).
 * It also includes info/routines for constructing the final ack to be sent to
 * the sender of the update message.
 *
 * It is expected to have one sub-class per assured replication sub mode.
 */
public abstract class ExpectedAcksInfo
{
  // The server handler of the server that sent the assured update message and
  // to whow we want to return the final ack
  private ServerHandler requesterServerHandler = null;
  // The requested assured mode of matcching update message
  private AssuredMode assuredMode = null;
  /**
   * The change number of the assured update message we want acks for.
   */
  protected ChangeNumber changeNumber = null;
  /**
   * Is the treatment of the acks for the update message completed or not ?
   * This is used for concurrent access to this object by either the assured
   * timeout task or the code for processing an ack for the matching update
   * message. This should be set to true when the treatment of the expected
   * acks is completed or an ack timeout has occured and we are going to remove
   * this object from the map where it is stored.
   */
  private boolean completed = false;
  /**
   * Creates a new ExpectedAcksInfo.
   * @param changeNumber The change number of the assured update message
   * @param requesterServerHandler The server handler of the server that sent
   * the assured update message
   * @param assuredMode The assured mode requested by the assured update message
   */
  protected ExpectedAcksInfo(ChangeNumber changeNumber,
    ServerHandler requesterServerHandler, AssuredMode assuredMode)
  {
    this.requesterServerHandler = requesterServerHandler;
    this.assuredMode = assuredMode;
    this.changeNumber = changeNumber;
  }
  /**
   * Gets the server handler of the server which requested the acknowledgments.
   * @return The server handler of the server which requested the
   * acknowledgments.
   */
  public ServerHandler getRequesterServer()
  {
    return requesterServerHandler;
  }
  /**
   * Gets the requested assured mode for the matching update message.
   * @return The requested assured mode for the matching update message.
   */
  public AssuredMode getAssuredMode()
  {
    return assuredMode;
  }
  /**
   * Process the received ack from a server we are waiting an ack from.
   * @param ackingServer The server handler of the server that sent the ack
   * @param ackMsg The ack message to process
   * @return True if the expected number of acks has just been reached
   */
  public abstract boolean processReceivedAck(ServerHandler ackingServer,
    AckMsg ackMsg);
  /**
   * Creates the ack message to be returned to the requester server, taking into
   * account the information in the received acks from every servers.
   * @param timeout True if we call this method when the timeout occurred, that
   * is we did not received every expected acks in time, and thus, the timeout
   * flag should also be enabled in the returned ack message.
   * @return The ack message ready to be sent to the requester server
   */
  public abstract AckMsg createAck(boolean timeout);
  /**
   * Has the treatment of this object been completed or not?
   * If true is returned, one must not modify this object (useless) nor remove
   * it from the map where it is stored (will be or has already been done by the
   * other code (ack timeout code, or ack processing code)).
   * @return True if treatment of this object has been completed.
   */
  public boolean isCompleted()
  {
    return completed;
  }
  /**
   * Signal that treatment of this object has been completed and that it is
   * going to be removed from the map where it is stored.
   */
  public void completed()
  {
    completed = true;
  }
}
opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java
@@ -254,7 +254,7 @@
    attributes.add(Attributes.create("server-id",
        String.valueOf(serverId)));
    attributes.add(Attributes.create("base-dn",
        rsDomain.getBaseDn().toNormalizedString()));
        rsDomain.getBaseDn()));
    attributes.add(Attributes.create("connected-to",
        replServerHandler.getMonitorInstanceName()));
opends/src/server/org/opends/server/replication/server/MonitorData.java
@@ -69,6 +69,10 @@
  private ConcurrentHashMap<Short, ServerState> LDAPStates =
    new ConcurrentHashMap<Short, ServerState>();
  // A Map containing the ServerStates of each RS.
  private ConcurrentHashMap<Short, ServerState> RSStates =
    new ConcurrentHashMap<Short, ServerState>();
  // For each LDAP server, the last(max) CN it published
  private ConcurrentHashMap<Short, ChangeNumber> maxCNs =
    new ConcurrentHashMap<Short, ChangeNumber>();
@@ -86,6 +90,9 @@
  private ConcurrentHashMap<Short, Long> fmRSDate =
    new ConcurrentHashMap<Short, Long>();
  private ConcurrentHashMap<Short, Long> missingChangesRS =
    new ConcurrentHashMap<Short, Long>();
  /**
   * Get an approximation of the latency delay of the replication.
@@ -129,13 +136,29 @@
  }
  /**
   * Get the number of missing changes for a Replication Server.
   *
   * @param serverId   The server ID.
   *
   * @return           The number of missing changes.
   */
  public long getMissingChangesRS(short serverId)
  {
    Long res = missingChangesRS.get(serverId);
    if (res==null)
      return 0;
    else
      return res;
  }
  /**
   * Build the monitor data that are computed from the collected ones.
   */
  public void completeComputing()
  {
    String mds = "";
    // Computes the missing changes counters
    // Computes the missing changes counters for LDAP servers
    // For each LSi ,
    //   Regarding each other LSj
    //    Sum the difference : max(LSj) - state(LSi)
@@ -167,6 +190,36 @@
      }
      mds += "=" + lsiMissingChanges;
      this.missingChanges.put(lsiSid,lsiMissingChanges);
    }
    // Computes the missing changes counters for RS :
    // Sum the difference of seqnuence numbers for each element in the States.
    for (short lsiSid : RSStates.keySet())
    {
      ServerState lsiState = this.RSStates.get(lsiSid);
      Long lsiMissingChanges = (long)0;
      if (lsiState != null)
      {
        Iterator<Short> lsjMaxItr = this.maxCNs.keySet().iterator();
        while (lsjMaxItr.hasNext())
        {
          Short lsjSid = lsjMaxItr.next();
          ChangeNumber lsjMaxCN = this.maxCNs.get(lsjSid);
          ChangeNumber lsiLastCN = lsiState.getMaxChangeNumber(lsjSid);
          int missingChangesLsiLsj =
            ChangeNumber.diffSeqNum(lsjMaxCN, lsiLastCN);
          mds +=
            "+ diff("+lsjMaxCN+"-"
                     +lsiLastCN+")="+missingChangesLsiLsj;
          lsiMissingChanges += missingChangesLsiLsj;
        }
      }
      mds += "=" + lsiMissingChanges;
      this.missingChangesRS.put(lsiSid,lsiMissingChanges);
      if (debugEnabled())
        TRACER.debugInfo(
@@ -307,6 +360,17 @@
  }
  /**
   * Set the state of the RS with the provided serverId.
   *
   * @param serverId   The server ID.
   * @param state      The server state.
   */
  public void setRSState(short serverId, ServerState state)
  {
    RSStates.put(serverId, state);
  }
  /**
   * Set the state of the LDAP server with the provided serverId.
   * @param serverId The server ID.
   * @param newFmd The first missing date.
opends/src/server/org/opends/server/replication/server/NotAssuredUpdateMsg.java
New file
@@ -0,0 +1,358 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.server;
import java.io.UnsupportedEncodingException;
import java.util.zip.DataFormatException;
import org.opends.server.replication.common.AssuredMode;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.replication.protocol.LDAPUpdateMsg;
import org.opends.server.replication.protocol.ProtocolVersion;
/**
 * This is a facility class that is in fact an hack to optimize replication
 * server performances in case of assured replication usage:
 * When received from a server by a server reader, an update message is to be
 * posted in the queues of the server writers. Thus, they receive the same
 * reference of update message. As we want to transform an assured update
 * message to an equivalent not assured one for some servers but not for all,
 * instead of performing a painful clone of the message, we use this special
 * class to keep a reference to the real object, but that will overwrite the
 * assured flag value to false when serializing the message, and return false
 * when calling the isAssured() method.
 *
 */
public class NotAssuredUpdateMsg extends UpdateMsg
{
  // The real update message this message represents
  private UpdateMsg realUpdateMsg = null;
  /**
   * Creates a new empty UpdateMsg.
   * This class is only used by replication server code so constructor is not
   * public by security.
   * @param updateMsg The real underlying update message this object represents.
   */
  NotAssuredUpdateMsg(UpdateMsg updateMsg)
  {
    realUpdateMsg = updateMsg;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public ChangeNumber getChangeNumber()
  {
    return realUpdateMsg.getChangeNumber();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAssured()
  {
    // Always return false as we represent a not assured message
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void setAssured(boolean assured)
  {
    // No impact for this method as semantic is that assured is always false
    // and we do not want to change the original real update message settings
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean equals(Object obj)
  {
    // Compare with the underlying real update message
    if (obj != null)
    {
      if (obj.getClass() != realUpdateMsg.getClass())
        return false;
      return realUpdateMsg.getChangeNumber().
        equals(((UpdateMsg)obj).getChangeNumber());
    }
    else
    {
      return false;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode()
  {
    return realUpdateMsg.hashCode();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public int compareTo(UpdateMsg msg)
  {
    return realUpdateMsg.compareTo(msg);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public byte[] getBytes(short reqProtocolVersion)
    throws UnsupportedEncodingException
  {
    // Get the encoding of the real message then overwrite the assured flag byte
    // to always be false
    byte[] bytes = realUpdateMsg.getBytes(reqProtocolVersion);
    int maxLen = bytes.length;
    int pos = -1;
    int nZeroFound = 0; // Number of 0 value found
    boolean found = false;
    /**
     * Overwrite the assured flag at the right position according to
     * message type.
     */
    // Look for assured flag position
    if (realUpdateMsg instanceof LDAPUpdateMsg)
    {
      // LDAP update message
      switch (reqProtocolVersion)
      {
        case ProtocolVersion.REPLICATION_PROTOCOL_V1:
          /* The message header is stored in the form :
           * <operation type><changenumber><dn><assured><entryuuid><change>
           * the length of result byte array is therefore :
           *   1 + change number length + 1 + dn length + 1  + 1 +
           *   uuid length + 1 + additional_length
           * See LDAPUpdateMsg.encodeHeader_V1() for more information
           */
          // Find end of change number then end of dn
          for (pos = 1 ; pos < maxLen ; pos++ ) {
            if (bytes[pos] == (byte)0)
            {
              nZeroFound++;
              if (nZeroFound == 2) // 2 end of string to find
              {
                found = true;
                break;
              }
            }
          }
          if (!found)
            throw new UnsupportedEncodingException("Could not find end of " +
              "change number.");
          pos++;
          if (pos >= maxLen)
            throw new UnsupportedEncodingException("Reached end of packet.");
          // Force assured flag to false
          bytes[pos] = (byte)0;
          break;
        case ProtocolVersion.REPLICATION_PROTOCOL_V2:
          /* The message header is stored in the form :
           * <operation type><protocol version><changenumber><dn><entryuuid>
           * <assured> <assured mode> <safe data level>
           * the length of result byte array is therefore :
           *   1 + 1 + change number length + 1 + dn length + 1 + uuid length +
           *   1 + 1 + 1 + 1 + additional_length
           * See LDAPUpdateMsg.encodeHeader() for more information
           */
          // Find end of change number then end of dn then end of uuid
          for (pos = 2 ; pos < maxLen ; pos++ ) {
            if (bytes[pos] == (byte)0)
            {
              nZeroFound++;
              if (nZeroFound == 3) // 3 end of string to find
              {
                found = true;
                break;
              }
            }
          }
          if (!found)
            throw new UnsupportedEncodingException("Could not find end of " +
              "change number.");
          pos++;
          if (pos >= maxLen)
            throw new UnsupportedEncodingException("Reached end of packet.");
          // Force assured flag to false
          bytes[pos] = (byte)0;
          break;
        default:
          throw new UnsupportedEncodingException("Unsupported requested " +
            " protocol version: " + reqProtocolVersion);
      }
    } else
    {
      if (!(realUpdateMsg instanceof UpdateMsg))
      {
        // Should never happen
        throw new UnsupportedEncodingException(
          "Unknown underlying real message type.");
      }
      // This is a generic update message
      /* The message header is stored in the form :
       * <operation type><protocol version><changenumber><assured>
       * <assured mode> <safe data level>
       * the length of result byte array is therefore :
       *   1 + 1 + change number length + 1 + 1
       *   + 1 + 1 + additional_length
       * See UpdateMsg.encodeHeader() for more  information
       */
      // Find end of change number
      for (pos = 2 ; pos < maxLen ; pos++ )
      {
        if (bytes[pos] == (byte)0)
        {
          nZeroFound++;
          if (nZeroFound == 1) // 1 end of string to find
          {
            found = true;
            break;
          }
        }
      }
      if (!found)
        throw new UnsupportedEncodingException("Could not find end of " +
          "change number.");
      pos++;
      if (pos >= maxLen)
        throw new UnsupportedEncodingException("Reached end of packet.");
      // Force assured flag to false
      bytes[pos] = (byte) 0;
    }
    return bytes;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public byte[] getBytes() throws UnsupportedEncodingException
  {
    return getBytes(ProtocolVersion.getCurrentVersion());
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public AssuredMode getAssuredMode()
  {
    return realUpdateMsg.getAssuredMode();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public byte getSafeDataLevel()
  {
    return realUpdateMsg.getSafeDataLevel();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void setAssuredMode(AssuredMode assuredMode)
  {
    // No impact for this method as semantic is that assured is always false
    // and we do not want to change the original real update message settings
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void setSafeDataLevel(byte safeDataLevel)
  {
    // No impact for this method as semantic is that assured is always false
    // and we do not want to change the original real update message settings
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public short getVersion()
  {
    return realUpdateMsg.getVersion();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public int size()
  {
    return realUpdateMsg.size();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  protected byte[] encodeHeader(byte type, int additionalLength)
    throws UnsupportedEncodingException
  {
    // No called as only used by constructors using bytes
    return null;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public int decodeHeader(byte type, byte[] encodedMsg)
                          throws DataFormatException
  {
    // No called as only used by getBytes methods
    return -1;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public byte[] getPayload()
  {
    return realUpdateMsg.getPayload();
  }
}
opends/src/server/org/opends/server/replication/server/ReplServerAckMessageList.java
File was deleted
opends/src/server/org/opends/server/replication/server/ReplicationBackend.java
@@ -34,6 +34,8 @@
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.util.StaticUtils.*;
import org.opends.server.replication.protocol.LDAPUpdateMsg;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -858,7 +860,7 @@
  /**
   * Export one change.
   */
  private void processChange(UpdateMsg msg,
  private void processChange(UpdateMsg updateMsg,
      LDIFExportConfig exportConfig, LDIFWriter ldifWriter,
      SearchOperation searchOperation, String baseDN)
  {
@@ -873,159 +875,165 @@
    try
    {
      if (msg instanceof AddMsg)
      if (updateMsg instanceof LDAPUpdateMsg)
      {
        AddMsg addMsg = (AddMsg)msg;
        AddOperation addOperation = (AddOperation)msg.createOperation(conn);
        LDAPUpdateMsg msg = (LDAPUpdateMsg) updateMsg;
        dn = DN.decode("puid=" + addMsg.getParentUid() + "+" +
            CHANGE_NUMBER + "=" + msg.getChangeNumber().toString() + "+" +
            msg.getDn() + "," + BASE_DN);
        Map<AttributeType,List<Attribute>> attributes =
          new HashMap<AttributeType,List<Attribute>>();
        Map<ObjectClass, String> objectclasses =
          new HashMap<ObjectClass, String>();
        for (RawAttribute a : addOperation.getRawAttributes())
        if (msg instanceof AddMsg)
        {
          Attribute attr = a.toAttribute();
          if (attr.getAttributeType().isObjectClassType())
          {
            for (ByteString os : a.getValues())
            {
              String ocName = os.toString();
              ObjectClass oc =
                DirectoryServer.getObjectClass(toLowerCase(ocName));
              if (oc == null)
              {
                oc = DirectoryServer.getDefaultObjectClass(ocName);
              }
          AddMsg addMsg = (AddMsg)msg;
          AddOperation addOperation = (AddOperation)msg.createOperation(conn);
              objectclasses.put(oc,ocName);
          dn = DN.decode("puid=" + addMsg.getParentUid() + "+" +
              CHANGE_NUMBER + "=" + msg.getChangeNumber().toString() + "+" +
              msg.getDn() + "," + BASE_DN);
          Map<AttributeType,List<Attribute>> attributes =
            new HashMap<AttributeType,List<Attribute>>();
          Map<ObjectClass, String> objectclasses =
            new HashMap<ObjectClass, String>();
          for (RawAttribute a : addOperation.getRawAttributes())
          {
            Attribute attr = a.toAttribute();
            if (attr.getAttributeType().isObjectClassType())
            {
              for (ByteString os : a.getValues())
              {
                String ocName = os.toString();
                ObjectClass oc =
                  DirectoryServer.getObjectClass(toLowerCase(ocName));
                if (oc == null)
                {
                  oc = DirectoryServer.getDefaultObjectClass(ocName);
                }
                objectclasses.put(oc,ocName);
              }
            }
            else
            {
              addAttribute(attributes, attr);
            }
          }
          Attribute changetype = Attributes.create("changetype", "add");
          addAttribute(attributes, changetype);
          if (exportConfig != null)
          {
            AddChangeRecordEntry changeRecord =
              new AddChangeRecordEntry(dn, attributes);
            ldifWriter.writeChangeRecord(changeRecord);
          }
          else
          {
            addAttribute(attributes, attr);
            entry = new Entry(dn, objectclasses, attributes, null);
          }
        }
        else if (msg instanceof DeleteMsg)
        {
          DeleteMsg delMsg = (DeleteMsg)msg;
          dn = DN.decode("uuid=" + msg.getUniqueId() + "," +
              CHANGE_NUMBER + "=" + delMsg.getChangeNumber().toString()+ "," +
              msg.getDn() +","+ BASE_DN);
          DeleteChangeRecordEntry changeRecord =
            new DeleteChangeRecordEntry(dn);
          if (exportConfig != null)
          {
            ldifWriter.writeChangeRecord(changeRecord);
          }
          else
          {
            Writer writer = new Writer();
            LDIFWriter ldifWriter2 = writer.getLDIFWriter();
            ldifWriter2.writeChangeRecord(changeRecord);
            LDIFReader reader = writer.getLDIFReader();
            entry = reader.readEntry();
          }
        }
        else if (msg instanceof ModifyMsg)
        {
          ModifyOperation op = (ModifyOperation)msg.createOperation(conn);
          dn = DN.decode("uuid=" + msg.getUniqueId() + "," +
              CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," +
              msg.getDn() +","+ BASE_DN);
          op.setInternalOperation(true);
          ModifyChangeRecordEntry changeRecord =
            new ModifyChangeRecordEntry(dn, op.getRawModifications());
          if (exportConfig != null)
          {
            ldifWriter.writeChangeRecord(changeRecord);
          }
          else
          {
            Writer writer = new Writer();
            LDIFWriter ldifWriter2 = writer.getLDIFWriter();
            ldifWriter2.writeChangeRecord(changeRecord);
            LDIFReader reader = writer.getLDIFReader();
            entry = reader.readEntry();
          }
        }
        else if (msg instanceof ModifyDNMsg)
        {
          ModifyDNOperation op = (ModifyDNOperation)msg.createOperation(conn);
          dn = DN.decode("uuid=" + msg.getUniqueId() + "," +
              CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," +
              msg.getDn() +","+ BASE_DN);
          op.setInternalOperation(true);
          ModifyDNChangeRecordEntry changeRecord =
            new ModifyDNChangeRecordEntry(dn, op.getNewRDN(), op.deleteOldRDN(),
                op.getNewSuperior());
          if (exportConfig != null)
          {
            ldifWriter.writeChangeRecord(changeRecord);
          }
          else
          {
            Writer writer = new Writer();
            LDIFWriter ldifWriter2 = writer.getLDIFWriter();
            ldifWriter2.writeChangeRecord(changeRecord);
            LDIFReader reader = writer.getLDIFReader();
            Entry modDNEntry = reader.readEntry();
            entry = modDNEntry;
          }
        }
        Attribute changetype = Attributes.create("changetype", "add");
        addAttribute(attributes, changetype);
        if (exportConfig != null)
        {
          AddChangeRecordEntry changeRecord =
            new AddChangeRecordEntry(dn, attributes);
          ldifWriter.writeChangeRecord(changeRecord);
          this.exportedCount++;
        }
        else
        {
          entry = new Entry(dn, objectclasses, attributes, null);
        }
      }
      else if (msg instanceof DeleteMsg)
      {
        DeleteMsg delMsg = (DeleteMsg)msg;
          // Add extensibleObject objectclass and the ChangeNumber
          // in the entry.
          if (!entry.getObjectClasses().containsKey(objectclass))
            entry.addObjectClass(objectclass);
          Attribute changeNumber =
            Attributes.create(CHANGE_NUMBER,
                msg.getChangeNumber().toStringUI());
          addAttribute(entry.getUserAttributes(), changeNumber);
          Attribute domain = Attributes.create("replicationDomain", baseDN);
          addAttribute(entry.getUserAttributes(), domain);
        dn = DN.decode("uuid=" + msg.getUniqueId() + "," +
            CHANGE_NUMBER + "=" + delMsg.getChangeNumber().toString()+ "," +
            msg.getDn() +","+ BASE_DN);
          // Get the base DN, scope, and filter for the search.
          DN  searchBaseDN = searchOperation.getBaseDN();
          SearchScope  scope  = searchOperation.getScope();
          SearchFilter filter = searchOperation.getFilter();
        DeleteChangeRecordEntry changeRecord =
          new DeleteChangeRecordEntry(dn);
        if (exportConfig != null)
        {
          ldifWriter.writeChangeRecord(changeRecord);
        }
        else
        {
          Writer writer = new Writer();
          LDIFWriter ldifWriter2 = writer.getLDIFWriter();
          ldifWriter2.writeChangeRecord(changeRecord);
          LDIFReader reader = writer.getLDIFReader();
          entry = reader.readEntry();
        }
      }
      else if (msg instanceof ModifyMsg)
      {
        ModifyOperation op = (ModifyOperation)msg.createOperation(conn);
        dn = DN.decode("uuid=" + msg.getUniqueId() + "," +
            CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," +
            msg.getDn() +","+ BASE_DN);
        op.setInternalOperation(true);
        ModifyChangeRecordEntry changeRecord =
          new ModifyChangeRecordEntry(dn, op.getRawModifications());
        if (exportConfig != null)
        {
          ldifWriter.writeChangeRecord(changeRecord);
        }
        else
        {
          Writer writer = new Writer();
          LDIFWriter ldifWriter2 = writer.getLDIFWriter();
          ldifWriter2.writeChangeRecord(changeRecord);
          LDIFReader reader = writer.getLDIFReader();
          entry = reader.readEntry();
        }
      }
      else if (msg instanceof ModifyDNMsg)
      {
        ModifyDNOperation op = (ModifyDNOperation)msg.createOperation(conn);
        dn = DN.decode("uuid=" + msg.getUniqueId() + "," +
            CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," +
            msg.getDn() +","+ BASE_DN);
        op.setInternalOperation(true);
        ModifyDNChangeRecordEntry changeRecord =
          new ModifyDNChangeRecordEntry(dn, op.getNewRDN(), op.deleteOldRDN(),
              op.getNewSuperior());
        if (exportConfig != null)
        {
          ldifWriter.writeChangeRecord(changeRecord);
        }
        else
        {
          Writer writer = new Writer();
          LDIFWriter ldifWriter2 = writer.getLDIFWriter();
          ldifWriter2.writeChangeRecord(changeRecord);
          LDIFReader reader = writer.getLDIFReader();
          Entry modDNEntry = reader.readEntry();
          entry = modDNEntry;
        }
      }
      if (exportConfig != null)
      {
        this.exportedCount++;
      }
      else
      {
        // Add extensibleObject objectclass and the ChangeNumber
        // in the entry.
        if (!entry.getObjectClasses().containsKey(objectclass))
          entry.addObjectClass(objectclass);
        Attribute changeNumber =
          Attributes.create(CHANGE_NUMBER, msg.getChangeNumber().toStringUI());
        addAttribute(entry.getUserAttributes(), changeNumber);
        Attribute domain = Attributes.create("replicationDomain", baseDN);
        addAttribute(entry.getUserAttributes(), domain);
        // Get the base DN, scope, and filter for the search.
        DN  searchBaseDN = searchOperation.getBaseDN();
        SearchScope  scope  = searchOperation.getScope();
        SearchFilter filter = searchOperation.getFilter();
        boolean ms = entry.matchesBaseAndScope(searchBaseDN, scope);
        boolean mf = filter.matchesEntry(entry);
        if ( ms && mf )
        {
          searchOperation.returnEntry(entry, new LinkedList<Control>());
          boolean ms = entry.matchesBaseAndScope(searchBaseDN, scope);
          boolean mf = filter.matchesEntry(entry);
          if ( ms && mf )
          {
            searchOperation.returnEntry(entry, new LinkedList<Control>());
          }
        }
      }
    }
opends/src/server/org/opends/server/replication/server/ReplicationDB.java
@@ -34,7 +34,6 @@
import java.util.List;
import java.io.UnsupportedEncodingException;
import org.opends.server.types.DN;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.protocol.UpdateMsg;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -59,7 +58,7 @@
  private ReplicationDbEnv dbenv = null;
  private ReplicationServer replicationServer;
  private Short serverId;
  private DN baseDn;
  private String baseDn;
  // The maximum number of retries in case of DatabaseDeadlock Exception.
  private static final int DEADLOCK_RETRIES = 10;
@@ -77,7 +76,7 @@
   * @param dbenv The Db environment to use to create the db.
   * @throws DatabaseException If a database problem happened.
   */
  public ReplicationDB(Short serverId, DN baseDn,
  public ReplicationDB(Short serverId, String baseDn,
                     ReplicationServer replicationServer,
                     ReplicationDbEnv dbenv)
                     throws DatabaseException
opends/src/server/org/opends/server/replication/server/ReplicationDbEnv.java
@@ -37,9 +37,6 @@
import java.io.File;
import java.io.UnsupportedEncodingException;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
@@ -148,7 +145,7 @@
          {
            long generationId=-1;
            DN baseDn;
            String baseDn;
            try
            {
@@ -165,18 +162,7 @@
                  + "<" + str[1] + ">"));
            }
            // <baseDn>
            baseDn = null;
            try
            {
              baseDn = DN.decode(str[2]);
            } catch (DirectoryException e)
            {
              Message message =
                ERR_IGNORE_BAD_DN_IN_DATABASE_IDENTIFIER.get(str[1]);
              logError(message);
            }
            baseDn = str[2];
            if (debugEnabled())
              TRACER.debugInfo(
@@ -239,16 +225,7 @@
                + "<" + str[0] + ">"));
          }
          // <baseDn>
          DN baseDn = null;
          try
          {
            baseDn = DN.decode(str[1]);
          } catch (DirectoryException e)
          {
            Message message =
              ERR_IGNORE_BAD_DN_IN_DATABASE_IDENTIFIER.get(str[1]);
            logError(message);
          }
          String baseDn = str[1];
          if (debugEnabled())
            TRACER.debugInfo(
@@ -287,7 +264,7 @@
     * @return the Database.
     * @throws DatabaseException in case of underlying Exception.
     */
    public Database getOrAddDb(Short serverId, DN baseDn, Long generationId)
    public Database getOrAddDb(Short serverId, String baseDn, Long generationId)
    throws DatabaseException
    {
      if (debugEnabled())
@@ -295,8 +272,7 @@
          serverId + " " + baseDn + " " + generationId);
      try
      {
        String stringId = serverId.toString() + FIELD_SEPARATOR
                          + baseDn.toNormalizedString();
        String stringId = serverId.toString() + FIELD_SEPARATOR + baseDn;
        // Opens the database for the changes received from this server
        // on this domain. Create it if it does not already exist.
@@ -333,11 +309,9 @@
        // Creates the record domain base Dn/ generationId in the stateDb
        // if it does not already exist.
        stringId = GENERATION_ID_TAG + FIELD_SEPARATOR +
          baseDn.toNormalizedString();
        stringId = GENERATION_ID_TAG + FIELD_SEPARATOR + baseDn;
        String dataStringId = GENERATION_ID_TAG + FIELD_SEPARATOR +
        generationId.toString() + FIELD_SEPARATOR +
          baseDn.toNormalizedString();
        generationId.toString() + FIELD_SEPARATOR + baseDn;
        byteId = stringId.getBytes("UTF-8");
        byte[] dataByteId;
        dataByteId = dataStringId.getBytes("UTF-8");
@@ -408,7 +382,7 @@
     * @param baseDn The baseDn for which the generationID must be cleared.
     *
     */
    public void clearGenerationId(DN baseDn)
    public void clearGenerationId(String baseDn)
    {
      if (debugEnabled())
        TRACER.debugInfo(
@@ -417,8 +391,7 @@
      try
      {
        // Deletes the record domain base Dn/ generationId in the stateDb
        String stringId = GENERATION_ID_TAG + FIELD_SEPARATOR +
          baseDn.toNormalizedString();
        String stringId = GENERATION_ID_TAG + FIELD_SEPARATOR + baseDn;
        byte[] byteId = stringId.getBytes("UTF-8");
        DatabaseEntry key = new DatabaseEntry();
        key.setData(byteId);
@@ -435,8 +408,7 @@
            if (debugEnabled())
              TRACER.debugInfo(
                "In " + this.replicationServer.getMonitorInstanceName() +
                " clearGenerationId (" +
                baseDn +") succeeded.");
                " clearGenerationId (" + baseDn +") succeeded.");
          }
          catch (DatabaseException dbe)
          {
@@ -472,7 +444,7 @@
     * @param serverId The serverId to remove from the Db.
     *
     */
    public void clearServerId(DN baseDn, Short serverId)
    public void clearServerId(String baseDn, Short serverId)
    {
      if (debugEnabled())
        TRACER.debugInfo(
@@ -480,8 +452,7 @@
            "clearServerId(baseDN=" + baseDn + ", serverId=" + serverId);
      try
      {
        String stringId = serverId.toString() + FIELD_SEPARATOR
                         + baseDn.toNormalizedString();
        String stringId = serverId.toString() + FIELD_SEPARATOR + baseDn;
        // Deletes the record serverId/domain base Dn in the stateDb
        byte[] byteId;
opends/src/server/org/opends/server/replication/server/ReplicationServer.java
@@ -108,8 +108,8 @@
  /* This table is used to store the list of dn for which we are currently
   * handling servers.
   */
  private ConcurrentHashMap<DN, ReplicationServerDomain> baseDNs =
          new ConcurrentHashMap<DN, ReplicationServerDomain>();
  private ConcurrentHashMap<String, ReplicationServerDomain> baseDNs =
          new ConcurrentHashMap<String, ReplicationServerDomain>();
  private String localURL = "null";
  private boolean shutdown = false;
@@ -202,7 +202,7 @@
    assuredTimeout = configuration.getAssuredTimeout();
    degradedStatusThreshold = configuration.getDegradedStatusThreshold();
    replSessionSecurity = new ReplSessionSecurity(configuration);
    replSessionSecurity = new ReplSessionSecurity();
    initialize(replicationPort);
    configuration.addChangeListener(this);
    DirectoryServer.registerMonitorProvider(this);
@@ -360,7 +360,7 @@
   *                    colon.
   * @param baseDn     The baseDn of the connection
   */
  private void connect(String serverURL, DN baseDn)
  private void connect(String serverURL, String baseDn)
  {
    int separator = serverURL.lastIndexOf(':');
    String port = serverURL.substring(separator + 1);
@@ -484,7 +484,7 @@
   * @return The ReplicationServerDomain associated to the base DN given in
   *         parameter.
   */
  public ReplicationServerDomain getReplicationServerDomain(DN baseDn,
  public ReplicationServerDomain getReplicationServerDomain(String baseDn,
          boolean create)
  {
    ReplicationServerDomain replicationServerDomain;
@@ -560,7 +560,7 @@
   *         DN given in parameter.
   * @throws DatabaseException in case of underlying database problem.
   */
  public DbHandler newDbHandler(short id, DN baseDn)
  public DbHandler newDbHandler(short id, String baseDn)
  throws DatabaseException
  {
    return new DbHandler(id, baseDn, this, dbEnv, queueSize);
@@ -572,7 +572,7 @@
   * @param  baseDn The baseDn for which to delete the generationId.
   * @throws DatabaseException When it occurs.
   */
  public void clearGenerationId(DN baseDn)
  public void clearGenerationId(String baseDn)
  throws DatabaseException
  {
    try
@@ -799,7 +799,7 @@
     * Add all the base DNs that are known by this replication server.
     */
    AttributeBuilder builder = new AttributeBuilder("base-dn");
    for (DN base : baseDNs.keySet())
    for (String base : baseDNs.keySet())
    {
      builder.add(base.toString());
    }
@@ -807,7 +807,7 @@
    // Publish to monitor the generation ID by replicationServerDomain
    builder = new AttributeBuilder("base-dn-generation-id");
    for (DN base : baseDNs.keySet())
    for (String base : baseDNs.keySet())
    {
      long generationId=-1;
      ReplicationServerDomain replicationServerDomain =
@@ -828,7 +828,7 @@
   * @param baseDN The baseDN of the replicationServerDomain.
   * @return The value of the generationID.
   */
  public long getGenerationId(DN baseDN)
  public long getGenerationId(String baseDN)
  {
    ReplicationServerDomain rsd =
            this.getReplicationServerDomain(baseDN, false);
opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
@@ -50,7 +50,6 @@
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.protocol.AckMsg;
import org.opends.server.replication.protocol.ErrorMsg;
import org.opends.server.replication.protocol.RoutableMsg;
import org.opends.server.replication.protocol.UpdateMsg;
@@ -58,17 +57,21 @@
import org.opends.server.replication.protocol.MonitorMsg;
import org.opends.server.replication.protocol.MonitorRequestMsg;
import org.opends.server.replication.protocol.ResetGenerationIdMsg;
import org.opends.server.types.DN;
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
@@ -88,9 +91,8 @@
 */
public class ReplicationServerDomain
{
  private final Object flowControlLock = new Object();
  private final DN baseDn;
  private final String baseDn;
  // The Status analyzer that periodically verifis if the connected DSs are
  // late or not
  private StatusAnalyzer statusAnalyzer = null;
@@ -154,16 +156,38 @@
  private MonitorData wrkMonitorData;
  /**
   * The needed info for each received assured update message we are waiting
   * acks for.
   * Key: a change number matching a received update message which requested
   * assured mode usage (either safe read or safe data mode)
   * Value: The object holding every info needed about the already received acks
   * as well as the acks to be received.
   * For more details, see ExpectedAcksInfo and its sub classes javadoc.
   */
  private final ConcurrentHashMap<ChangeNumber, ExpectedAcksInfo> waitingAcks =
    new ConcurrentHashMap<ChangeNumber, ExpectedAcksInfo>();
  // The timer used to run the timeout code (timer tasks) for the assured update
  // messages we are waiting acks for.
  private Timer assuredTimeoutTimer = null;
  // Counter used to purge the timer tasks referemces in assuredTimeoutTimer,
  // every n number of treated assured messages
  private int assuredTimeoutTimerPurgeCounter = 0;
  /**
   * Creates a new ReplicationServerDomain associated to the DN baseDn.
   *
   * @param baseDn The baseDn associated to the ReplicationServerDomain.
   * @param replicationServer the ReplicationServer that created this
   *                          replicationServer cache.
   */
  public ReplicationServerDomain(DN baseDn, ReplicationServer replicationServer)
  public ReplicationServerDomain(
      String baseDn, ReplicationServer replicationServer)
  {
    this.baseDn = baseDn;
    this.replicationServer = replicationServer;
    this.assuredTimeoutTimer = new Timer("Replication Assured Timer for " +
      baseDn + " in RS " + replicationServer.getServerId(), true);
  }
  /**
@@ -179,32 +203,11 @@
  public void put(UpdateMsg update, ServerHandler sourceHandler)
    throws IOException
  {
    /*
     * TODO : In case that the source server is a LDAP server this method
     * should check that change did get pushed to at least one
     * other replication server before pushing it to the LDAP servers
     */
    short id = update.getChangeNumber().getServerId();
    ChangeNumber cn = update.getChangeNumber();
    short id = cn.getServerId();
    sourceHandler.updateServerState(update);
    sourceHandler.incrementInCount();
    if (update.isAssured())
    {
      int count = this.NumServers();
      if (count > 1)
      {
        if (sourceHandler.isReplicationServer())
          ServerHandler.addWaitingAck(update, sourceHandler.getServerId(),
            this, count - 1);
        else
          sourceHandler.addWaitingAck(update, count - 1);
      } else
      {
        sourceHandler.sendAck(update.getChangeNumber());
      }
    }
    if (generationId < 0)
    {
      generationId = sourceHandler.getGenerationId();
@@ -244,11 +247,103 @@
    // Publish the messages to the source handler
    dbHandler.add(update);
    /**
     * If this is an assured message (a message requesting ack), we must
     * construct the ExpectedAcksInfo object with the right number of expected
     * acks before posting message to the writers. Otherwise some writers may
     * have time to post, receive the ack and increment received ack counter
     * (kept in ExpectedAcksInfo object) and we could think the acknowledgment
     * is fully processed although it may be not (some other acks from other
     * servers are not yet arrived). So for that purpose we do a pre-loop
     * to determine to who we will post an assured message.
     * Whether the assured mode is safe read or safe data, we anyway do not
     * support the assured replication feature across topologies with different
     * group ids. The assured feature insures assured replication based on the
     * same locality (group id). For instance in double data center deployment
     * (2 group id usage) with assured replication enabled, an assured message
     * sent from data center 1 (group id = 1) will be sent to servers of both
     * data centers, but one will request and wait acks only from servers of the
     * data center 1.
     */
    boolean assuredMessage = update.isAssured();
    PreparedAssuredInfo preparedAssuredInfo = null;
    if (assuredMessage)
    {
      // Assured feature is supported starting from replication protocol V2
      if (sourceHandler.getProtocolVersion() >=
        ProtocolVersion.REPLICATION_PROTOCOL_V2)
      {
        // According to assured sub-mode, prepare structures to keep track of
        // the acks we are interested in.
        AssuredMode assuredMode = update.getAssuredMode();
        if (assuredMode != AssuredMode.SAFE_DATA_MODE)
        {
          preparedAssuredInfo = processSafeDataUpdateMsg(update, sourceHandler);
        } else if (assuredMode != AssuredMode.SAFE_READ_MODE)
        {
          preparedAssuredInfo = processSafeReadUpdateMsg(update, sourceHandler);
        } else
        {
          // Unknown assured mode: should never happen
          Message errorMsg = ERR_RS_UNKNOWN_ASSURED_MODE.get(
            Short.toString(replicationServer.getServerId()),
            assuredMode.toString(), baseDn, update.toString());
          logError(errorMsg);
          assuredMessage = false;
        }
      } else
      {
        assuredMessage = false;
      }
    }
    List<Short> expectedServers = null;
    if (assuredMessage)
    {
      expectedServers = preparedAssuredInfo.expectedServers;
      if (expectedServers != null)
      {
        // Store the expected acks info into the global map.
        // The code for processing reception of acks for this update will update
        // info kept in this object and if enough acks received, it will send
        // back the final ack to the requester and remove the object from this
        // map
        // OR
        // The following timer will time out and send an timeout ack to the
        // requester if the acks are not received in time. The timer will also
        // remove the object from this map.
        waitingAcks.put(cn, preparedAssuredInfo.expectedAcksInfo);
        // Arm timer for this assured update message (wait for acks until it
        // times out)
        AssuredTimeoutTask assuredTimeoutTask = new AssuredTimeoutTask(cn);
        assuredTimeoutTimer.schedule(assuredTimeoutTask,
          replicationServer.getAssuredTimeout());
        // Purge timer every 100 treated messages
        assuredTimeoutTimerPurgeCounter++;
        if ((assuredTimeoutTimerPurgeCounter % 100) == 0)
          assuredTimeoutTimer.purge();
      }
    }
    /**
     * The update message equivalent to the originally received update message,
     * but with assured flag disabled. This message is the one that should be
     * sent to non elligible servers for assured mode.
     * We need a clone like of the original message with assured flag off, to be
     * posted to servers we don't want to wait the ack from (not normal status
     * servers or servers with different group id). This must be done because
     * the posted message is a reference so each writer queue gets the same
     * reference, thus, changing the assured flag of an object is done for every
     * references posted on every writer queues. That is why we need a message
     * version with assured flag on and another one with assured flag off.
     */
    NotAssuredUpdateMsg notAssuredUpdate = null;
    /*
     * Push the message to the replication servers
     */
    if (!sourceHandler.isReplicationServer())
    if (sourceHandler.isLDAPserver())
    {
      for (ServerHandler handler : replicationServers.values())
      {
@@ -261,7 +356,7 @@
          if (debugEnabled())
            TRACER.debugInfo("In RS " +
              replicationServer.getServerId() +
              " for dn " + baseDn.toNormalizedString() + ", update " +
              " for dn " + baseDn + ", update " +
              update.getChangeNumber().toString() +
              " will not be sent to replication server " +
              Short.toString(handler.getServerId()) + " with generation id " +
@@ -272,7 +367,27 @@
          continue;
        }
        handler.add(update, sourceHandler);
        if (assuredMessage)
        {
          // Assured mode: post an assured or not assured matching update
          // message according to what has been computed for the destination
          // server
          if ((expectedServers != null) && expectedServers.contains(handler.
            getServerId()))
          {
            handler.add(update, sourceHandler);
          } else
          {
            if (notAssuredUpdate == null)
            {
              notAssuredUpdate = new NotAssuredUpdateMsg(update);
            }
            handler.add(notAssuredUpdate, sourceHandler);
          }
        } else
        {
          handler.add(update, sourceHandler);
        }
      }
    }
@@ -281,7 +396,7 @@
     */
    for (ServerHandler handler : directoryServers.values())
    {
      // don't forward the change to the server that just sent it
      // Don't forward the change to the server that just sent it
      if (handler == sourceHandler)
      {
        continue;
@@ -307,7 +422,7 @@
          if (dsStatus == ServerStatus.BAD_GEN_ID_STATUS)
            TRACER.debugInfo("In RS " +
              replicationServer.getServerId() +
              " for dn " + baseDn.toNormalizedString() + ", update " +
              " for dn " + baseDn + ", update " +
              update.getChangeNumber().toString() +
              " will not be sent to directory server " +
              Short.toString(handler.getServerId()) + " with generation id " +
@@ -317,7 +432,7 @@
          if (dsStatus == ServerStatus.FULL_UPDATE_STATUS)
            TRACER.debugInfo("In RS " +
              replicationServer.getServerId() +
              " for dn " + baseDn.toNormalizedString() + ", update " +
              " for dn " + baseDn + ", update " +
              update.getChangeNumber().toString() +
              " will not be sent to directory server " +
              Short.toString(handler.getServerId()) +
@@ -327,9 +442,389 @@
        continue;
      }
      handler.add(update, sourceHandler);
      if (assuredMessage)
      {
        // Assured mode: post an assured or not assured matching update
        // message according to what has been computed for the destination
        // server
        if ((expectedServers != null) && expectedServers.contains(handler.
          getServerId()))
        {
          handler.add(update, sourceHandler);
        } else
        {
          if (notAssuredUpdate == null)
          {
            notAssuredUpdate = new NotAssuredUpdateMsg(update);
          }
          handler.add(notAssuredUpdate, sourceHandler);
        }
      } else
      {
        handler.add(update, sourceHandler);
      }
    }
  }
  /**
   * Helper class to be the return type of a method that processes a just
   * received assured update message:
   * - processSafeReadUpdateMsg
   * - processSafeDataUpdateMsg
   * This is a facility to pack many interesting returned object.
   */
  private class PreparedAssuredInfo
  {
      /**
       * The list of servers identified as servers we are interested in
       * receiving acks from. If this list is not null, then expectedAcksInfo
       * should be not null.
       * Servers that are not in this list are servers not elligible for an ack
       * request.
       *
       */
      public List<Short> expectedServers = null;
      /**
       * The constructed ExpectedAcksInfo object to be used when acks will be
       * received. Null if expectedServers is null.
       */
      public ExpectedAcksInfo expectedAcksInfo = null;
  }
  /**
   * Process a just received assured update message in Safe Read mode. If the
   * ack can be sent immediately, it is done here. This will also determine to
   * which suitable servers an ack should be requested from, and which ones are
   * not elligible for an ack request.
   * This method is an helper method for the put method. Have a look at the put
   * method for a better understanding.
   * @param update The just received assured update to process.
   * @param sourceHandler The ServerHandler for the server from which the
   *        update was received
   * @return A suitable PreparedAssuredInfo object that contains every needed
   * info to proceed with post to server writers.
   * @throws IOException When an IO exception happens during the update
   *         processing.
   */
  private PreparedAssuredInfo processSafeReadUpdateMsg(
    UpdateMsg update, ServerHandler sourceHandler) throws IOException
  {
    ChangeNumber cn = update.getChangeNumber();
    byte groupId = replicationServer.getGroupId();
    byte sourceGroupId = sourceHandler.getGroupId();
    List<Short> expectedServers = new ArrayList<Short>();
    List<Short> wrongStatusServers = new ArrayList<Short>();
    if (sourceGroupId != groupId)
      // Assured feature does not cross different group ids
    {
      if (sourceHandler.isLDAPserver())
      {
        // Look for RS elligible for assured
        for (ServerHandler handler : replicationServers.values())
        {
          if (handler.getGroupId() == groupId)
            expectedServers.add(handler.getServerId());
        }
      }
      // Look for DS elligible for assured
      for (ServerHandler handler : directoryServers.values())
      {
        // Don't forward the change to the server that just sent it
        if (handler == sourceHandler)
        {
          continue;
        }
        if (handler.getGroupId() == groupId)
        {
          if (handler.getStatus() == ServerStatus.NORMAL_STATUS)
          {
            expectedServers.add(handler.getServerId());
          } else
          {
            wrongStatusServers.add(handler.getServerId());
          }
        }
      }
    }
    // Return computed structures
    PreparedAssuredInfo preparedAssuredInfo = new PreparedAssuredInfo();
    if (expectedServers.size() > 0)
    {
      // Some other acks to wait for
      preparedAssuredInfo.expectedAcksInfo = new SafeReadExpectedAcksInfo(cn,
        sourceHandler, expectedServers, wrongStatusServers);
      preparedAssuredInfo.expectedServers = expectedServers;
    }
    if (preparedAssuredInfo.expectedServers == null)
    {
      // No elligible servers found, send the ack immediatly
      AckMsg ack = new AckMsg(cn);
      sourceHandler.sendAck(ack);
    }
    return preparedAssuredInfo;
  }
  /**
   * Process a just received assured update message in Safe Data mode. If the
   * ack can be sent immediately, it is done here. This will also determine to
   * which suitable servers an ack should be requested from, and which ones are
   * not elligible for an ack request.
   * This method is an helper method for the put method. Have a look at the put
   * method for a better understanding.
   * @param update The just received assured update to process.
   * @param sourceHandler The ServerHandler for the server from which the
   *        update was received
   * @return A suitable PreparedAssuredInfo object that contains every needed
   * info to proceed with post to server writers.
   * @throws IOException When an IO exception happens during the update
   *         processing.
   */
  private PreparedAssuredInfo processSafeDataUpdateMsg(
    UpdateMsg update, ServerHandler sourceHandler) throws IOException
  {
    ChangeNumber cn = update.getChangeNumber();
    boolean interestedInAcks = true;
    byte safeDataLevel = update.getSafeDataLevel();
    byte groupId = replicationServer.getGroupId();
    byte sourceGroupId = sourceHandler.getGroupId();
    if (safeDataLevel < (byte) 1)
    {
      // Should never happen
      Message errorMsg = ERR_UNKNOWN_ASSURED_SAFE_DATA_LEVEL.get(
        Short.toString(replicationServer.getServerId()),
        Byte.toString(safeDataLevel), baseDn, update.toString());
      logError(errorMsg);
      interestedInAcks = false;
    } else if (sourceGroupId != groupId)
    {
      // Assured feature does not cross different group ids
      interestedInAcks = false;
    } else
    {
      if (sourceHandler.isLDAPserver())
      {
        if (safeDataLevel == (byte) 1)
        {
          // Immediatly return the ack for an assured message in safe data mode
          // with safe data level 1, coming from a DS. No need to wait for more
          // acks
          AckMsg ack = new AckMsg(cn);
          sourceHandler.sendAck(ack);
          interestedInAcks = false; // No further acks to obtain
        } else
        {
          // level > 1 : We need further acks
          // The message will be posted in assured mode to elligible servers.
          // The embedded safe data level is not changed, and his value will be
          // used by a remote RS to determine if he must send an ack (level > 1)
          // or not (level = 1)
        }
      } else
      { // A RS sent us the safe data message, for sure no futher acks to wait
        interestedInAcks = false;
        if (safeDataLevel == (byte) 1)
        {
          // The original level was 1 so the RS that sent us this message should
          // have already sent his ack to the sender DS. Level 1 has already
          // been reached so no further acks to wait
          // This should not happen in theory as the sender RS server should
          // have sent us a matching not assured message so we should not come
          // to here.
        } else
        {
          // level > 1, so Ack this message to originator RS
          AckMsg ack = new AckMsg(cn);
          sourceHandler.sendAck(ack);
        }
      }
    }
    List<Short> expectedServers = new ArrayList<Short>();
    if (interestedInAcks)
    {
      if (sourceHandler.isLDAPserver())
      {
        // Look for RS elligible for assured
        for (ServerHandler handler : replicationServers.values())
        {
          if (handler.getGroupId() == groupId)
            expectedServers.add(handler.getServerId());
        }
      }
      // Look for DS elligible for assured
      for (ServerHandler handler : directoryServers.values())
      {
        // Don't forward the change to the server that just sent it
        if (handler == sourceHandler)
        {
          continue;
        }
        if (handler.getGroupId() == groupId)
          expectedServers.add(handler.getServerId());
      }
    }
    // Return computed structures
    PreparedAssuredInfo preparedAssuredInfo = new PreparedAssuredInfo();
    if (interestedInAcks && (expectedServers.size() > 0))
    {
      // Some other acks to wait for
      preparedAssuredInfo.expectedAcksInfo = new SafeDataExpectedAcksInfo(cn,
        sourceHandler, update.getSafeDataLevel());
      preparedAssuredInfo.expectedServers = expectedServers;
    }
    if (interestedInAcks && (preparedAssuredInfo.expectedServers == null))
    {
      // level > 1 and source is a DS but no elligible servers found, send the
      // ack immediatly
      AckMsg ack = new AckMsg(cn);
      sourceHandler.sendAck(ack);
    }
    return preparedAssuredInfo;
  }
  /**
   * Process an ack received from a given server.
   *
   * @param ack The ack message received.
   * @param ackingServer The server handler of the server that sent the ack.
   */
  public void processAck(AckMsg ack, ServerHandler ackingServer)
  {
    // Retrieve the expected acks info for the update matching the original
    // sent update.
    ChangeNumber cn = ack.getChangeNumber();
    ExpectedAcksInfo expectedAcksInfo = waitingAcks.get(cn);
    if (expectedAcksInfo != null)
    {
      // Prevent concurrent access from processAck() or AssuredTimeoutTask.run()
      synchronized (expectedAcksInfo)
      {
        if (expectedAcksInfo.isCompleted())
        {
          // Timeout code is sending a timeout ack, do nothing and let him
          // remove object from the map
          return;
        }
        // If this is the last ack we were waiting from, immediatly create and
        // send the final ack to the original server
        if (expectedAcksInfo.processReceivedAck(ackingServer, ack))
        {
          // Remove the object from the map as no more needed
          waitingAcks.remove(cn);
          AckMsg finalAck = expectedAcksInfo.createAck(false);
          ServerHandler origServer = expectedAcksInfo.getRequesterServer();
          try
          {
            origServer.sendAck(finalAck);
          } catch (IOException e)
          {
            /*
             * An error happened trying the send back an ack to the server.
             * Log an error and close the connection to this server.
             */
            MessageBuilder mb = new MessageBuilder();
            mb.append(ERR_RS_ERROR_SENDING_ACK.get(
              Short.toString(replicationServer.getServerId()),
              Short.toString(origServer.getServerId()), cn.toString(), baseDn));
            mb.append(stackTraceToSingleLineString(e));
            logError(mb.toMessage());
            stopServer(origServer);
          }
          // Mark the ack info object as completed to prevent potential timeout
          // code parallel run
          expectedAcksInfo.completed();
        }
      }
    } else
    {
      // The timeout occured for the update matching this change number and the
      // ack with timeout error has probably already been sent.
    }
  }
  /**
   * The code run when the timeout occurs while waiting for acks of the
   * elligible servers. This basically sends a timeout ack (with any additional
   * error info) to the original server that sent an assured update message.
   */
  private class AssuredTimeoutTask extends TimerTask
  {
    private ChangeNumber cn = null;
    /**
     * Constructor for the timer task.
     * @param cn The changenumber of the assured update we are waiting acks for
     */
    public AssuredTimeoutTask(ChangeNumber cn)
    {
      this.cn = cn;
    }
    /**
     * Run when the assured timeout for an assured update message we are waiting
     * acks for occurs.
     */
    public void run()
    {
      ExpectedAcksInfo expectedAcksInfo = waitingAcks.get(cn);
      if (expectedAcksInfo != null)
      {
        synchronized (expectedAcksInfo)
        {
          if (expectedAcksInfo.isCompleted())
          {
            // processAck() code is sending the ack, do nothing and let him
            // remove object from the map
            return;
          }
          // Remove the object from the map as no more needed
          waitingAcks.remove(cn);
          // Create the timeout ack and send him to the server the assured
          // update message came from
          AckMsg finalAck = expectedAcksInfo.createAck(true);
          ServerHandler origServer = expectedAcksInfo.getRequesterServer();
          if (debugEnabled())
            TRACER.debugInfo(
              "In RS " + Short.toString(replicationServer.getServerId()) +
              " for " + baseDn +
              ", sending timeout for assured update with change " + " number " +
              cn.toString() + " to server id " +
              Short.toString(origServer.getServerId()));
          try
          {
            origServer.sendAck(finalAck);
          } catch (IOException e)
          {
            /*
             * An error happened trying the send back an ack to the server.
             * Log an error and close the connection to this server.
             */
            MessageBuilder mb = new MessageBuilder();
            mb.append(ERR_RS_ERROR_SENDING_ACK.get(
              Short.toString(replicationServer.getServerId()),
              Short.toString(origServer.getServerId()), cn.toString(), baseDn));
            mb.append(stackTraceToSingleLineString(e));
            logError(mb.toMessage());
            stopServer(origServer);
          }
          // Mark the ack info object as completed to prevent potential
          // processAck() code parallel run
          expectedAcksInfo.completed();
        }
      }
    }
  }
  /**
@@ -687,7 +1182,7 @@
   * Get the baseDn.
   * @return Returns the baseDn.
   */
  public DN getBaseDn()
  public String getBaseDn()
  {
    return baseDn;
  }
@@ -711,44 +1206,6 @@
  }
  /**
   * Get the number of currently connected servers.
   *
   * @return the number of currently connected servers.
   */
  private int NumServers()
  {
    return replicationServers.size() + directoryServers.size();
  }
  /**
   * Add an ack to the list of ack received for a given change.
   *
   * @param message The ack message received.
   * @param fromServerId The identifier of the server that sent the ack.
   */
  public void ack(AckMsg message, short fromServerId)
  {
    /*
     * there are 2 possible cases here :
     *  - the message that was acked comes from a server to which
     *    we are directly connected.
     *    In this case, we can find the handler from the directoryServers map
     *  - the message that was acked comes from a server to which we are not
     *    connected.
     *    In this case we need to find the replication server that forwarded
     *    the change and send back the ack to this server.
     */
    ServerHandler handler = directoryServers.get(
      message.getChangeNumber().getServerId());
    if (handler != null)
      handler.ack(message, fromServerId);
    else
    {
      ServerHandler.ackChangelog(message, fromServerId);
    }
  }
  /**
   * Retrieves the destination handlers for a routable message.
   *
   * @param msg The message to route.
@@ -975,56 +1432,6 @@
  }
  /**
   * Send back an ack to the server that sent the change.
   *
   * @param changeNumber The ChangeNumber of the change that must be acked.
   * @param isLDAPserver This boolean indicates if the server that sent the
   *                     change was an LDAP server or a ReplicationServer.
   */
  public void sendAck(ChangeNumber changeNumber, boolean isLDAPserver)
  {
    short serverId = changeNumber.getServerId();
    sendAck(changeNumber, isLDAPserver, serverId);
  }
  /**
   *
   * Send back an ack to a server that sent the change.
   *
   * @param changeNumber The ChangeNumber of the change that must be acked.
   * @param isLDAPserver This boolean indicates if the server that sent the
   *                     change was an LDAP server or a ReplicationServer.
   * @param serverId     The identifier of the server from which we
   *                     received the change..
   */
  public void sendAck(ChangeNumber changeNumber, boolean isLDAPserver,
    short serverId)
  {
    ServerHandler handler;
    if (isLDAPserver)
      handler = directoryServers.get(serverId);
    else
      handler = replicationServers.get(serverId);
    // TODO : check for null handler and log error
    try
    {
      handler.sendAck(changeNumber);
    } catch (IOException e)
    {
      /*
       * An error happened trying the send back an ack to this server.
       * Log an error and close the connection to this server.
       */
      MessageBuilder mb = new MessageBuilder();
      mb.append(ERR_CHANGELOG_ERROR_SENDING_ACK.get(this.toString()));
      mb.append(stackTraceToSingleLineString(e));
      logError(mb.toMessage());
      stopServer(handler);
    }
  }
  /**
   * Shutdown this ReplicationServerDomain.
   */
  public void shutdown()
@@ -1228,13 +1635,8 @@
    {
      // Put RS info
      rsInfos.add(serverHandler.toRSInfo());
      // Put his DSs info
      Map<Short, LightweightServerHandler> lsList =
        serverHandler.getConnectedDSs();
      for (LightweightServerHandler ls : lsList.values())
      {
        dsInfos.add(ls.toDSInfo());
      }
      serverHandler.addDSInfos(dsInfos);
    }
    return new TopologyMsg(dsInfos, rsInfos);
@@ -1641,7 +2043,7 @@
    if (generationId > 0 && (generationId != handler.getGenerationId()))
    {
      Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
        baseDn.toNormalizedString(),
        baseDn,
        Short.toString(handler.getServerId()),
        Long.toString(handler.getGenerationId()),
        Long.toString(generationId));
@@ -1879,10 +2281,13 @@
      synchronized (wrkMonitorData)
      {
        // Here is the RS state : list <serverID, lastChangeNumber>
        // For each LDAP Server, we keep the max CN accross the RSes
        // For each LDAP Server, we keep the max CN across the RSes
        ServerState replServerState = msg.getReplServerDbState();
        wrkMonitorData.setMaxCNs(replServerState);
        // store the remote RS states.
        wrkMonitorData.setRSState(msg.getsenderID(), replServerState);
        // Store the remote LDAP servers states
        Iterator<Short> lsidIterator = msg.ldapIterator();
        while (lsidIterator.hasNext())
@@ -2092,3 +2497,4 @@
    }
  }
}
opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java
New file
@@ -0,0 +1,110 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.server;
import static org.opends.server.loggers.debug.DebugLogger.*;
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.protocol.AckMsg;
/**
 * This class holds every info needed about the expected acks for a received
 * update message requesting assured replication with Safe Data sub-mode.
 * It also includes info/routines for constructing the final ack to be sent to
 * the sender of the update message.
 */
public class SafeDataExpectedAcksInfo extends ExpectedAcksInfo
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // Requested level of safe data when the update message was received.
  private byte safeDataLevel = (byte)-1;
  // Number of received acks for the matching update message, up to now
  // Already set to 1 as the local RS receiving the message from a DS counts.
  private byte numReceivedAcks = (byte)1;
  /**
   * Creates a new SafeDataExpectedAcksInfo.
   * @param changeNumber The change number of the assured update message
   * @param requesterServerHandler The server that sent the assured update
   * message
   * @param safeDataLevel The Safe Data level requested for the assured
   * update message
   */
  public SafeDataExpectedAcksInfo(ChangeNumber changeNumber,
    ServerHandler requesterServerHandler, byte safeDataLevel)
  {
    super(changeNumber, requesterServerHandler, AssuredMode.SAFE_DATA_MODE);
    this.safeDataLevel = safeDataLevel;
  }
  /**
   * {@inheritDoc}
   */
   public boolean processReceivedAck(ServerHandler ackingServer, AckMsg ackMsg)
  {
    /*
     * Security: although a DS should not respond to an update message sent to
     * 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())
     {
       // Sanity check: this should never happen
        if (debugEnabled())
          TRACER.debugInfo("Received unexpected SD ack from DS id: "
          + ackingServer.getServerId() + " ack message: " + ackMsg);
        return false;
     }
    numReceivedAcks++;
    if (numReceivedAcks == safeDataLevel)
      return true;
    else
      return false;
  }
  /**
   * {@inheritDoc}
   */
  public AckMsg createAck(boolean timeout)
  {
    AckMsg ack = new AckMsg(changeNumber);
    if (timeout)
      ack.setHasTimeout(true);
    return ack;
  }
}
opends/src/server/org/opends/server/replication/server/SafeReadExpectedAcksInfo.java
New file
@@ -0,0 +1,240 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.server;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opends.server.replication.common.AssuredMode;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.protocol.AckMsg;
/**
 * This class holds every info needed about the expected acks for a received
 * update message requesting assured replication with Safe Read sub-mode.
 * It also includes info/routines for constructing the final ack to be sent to
 * the sender of the update message.
 */
public class SafeReadExpectedAcksInfo extends ExpectedAcksInfo
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // Did some servers go in timeout when the matching update was sent ?
  private boolean hasTimeout = false;
  // Were some servers in wrong status when the matching update was sent ?
  private boolean hasWrongStatus = false;
  // Did some servers make an error replaying the sent matching update ?
  private boolean hasReplayError = false;
  // The list of server ids that had errors for the sent matching update
  // Each server id of the list had one of the
  // 3 possible errors (timeout, wrong status or replay error)
  private List<Short> failedServers = null;
  /**
   * This gives the list of servers we are willing to wait acks from and the
   * information about the ack from the servers.
   * key: the id of the server.
   * value: a boolean true if we received the ack from the server,
   * false otherwise.
   * This must not include servers we already identified they are in wrong
   * status, but just servers that are in normal status.
   */
  private Map<Short,Boolean> expectedServersAckStatus =
    new HashMap<Short,Boolean>();
  /**
   * Number of servers we want an ack from and from which we received the ack.
   * Said differently: the number of servers in expectedServersAckStatus whose
   * value is true. When this value reaches the size of expectedServersAckStatus
   * we can compute an ack message (based on info in this object), to be
   * returned to the (requester) server that sent us an assured update message.
   */
  private int numKnownAckStatus = 0;
  /**
   * Creates a new SafeReadExpectedAcksInfo.
   * @param changeNumber The change number of the assured update message
   * @param requesterServerHandler The server that sent the assured update
   * message
   * @param expectedServers The list of servers we want an ack from (they are
   * in normal status and have the same group id as us)
   * @param wrongStatusServers The list of all servers already detected in
   * wrongStatus (degraded status) to keep trace of the error for the future
   * returning ack we gonna compute
   */
  public SafeReadExpectedAcksInfo(ChangeNumber changeNumber,
    ServerHandler requesterServerHandler, List<Short> expectedServers,
    List<Short> wrongStatusServers)
  {
    super(changeNumber, requesterServerHandler, AssuredMode.SAFE_READ_MODE);
    // Keep track of potential servers detected in wrong status
    if (wrongStatusServers.size() > 0)
    {
      hasWrongStatus = true;
      failedServers = wrongStatusServers;
    }
    // Initialize list of servers we expect acks from
    for (Short serverId : expectedServers)
    {
      expectedServersAckStatus.put(serverId, false);
    }
  }
  /**
   * Sets the timeout marker for the future update ack.
   * @param hasTimeout True if some timeout occurred
   */
  public void setHasTimeout(boolean hasTimeout)
  {
    this.hasTimeout = hasTimeout;
  }
  /**
   * Sets the wrong status marker for the future update ack.
   * @param hasWrongStatus True if some servers were in wrong status
   */
  public void setHasWrongStatus(boolean hasWrongStatus)
  {
    this.hasWrongStatus = hasWrongStatus;
  }
  /**
   * Sets the replay error marker for the future update ack.
   * @param hasReplayError True if some servers had errors replaying the change
   */
  public void setHasReplayError(boolean hasReplayError)
  {
    this.hasReplayError = hasReplayError;
  }
  /**
   * Gets the timeout marker for the future update ack.
   * @return The timeout marker for the future update ack.
   */
  public boolean hasTimeout()
  {
    return hasTimeout;
  }
  /**
   * Gets the wrong status marker for the future update ack.
   * @return hasWrongStatus The wrong status marker for the future update ack.
   */
  public boolean hasWrongStatus()
  {
    return hasWrongStatus;
  }
  /**
   * Gets the replay error marker for the future update ack.
   * @return hasReplayError The replay error marker for the future update ack.
   */
  public boolean hasReplayError()
  {
    return hasReplayError;
  }
  /**
   * {@inheritDoc}
   */
  public boolean processReceivedAck(ServerHandler ackingServer, AckMsg ackMsg)
  {
    // Get the ack status for the matching server
    short ackingServerId = ackingServer.getServerId();
    boolean ackReceived = expectedServersAckStatus.get(ackingServerId);
    if (ackReceived)
    {
      // Sanity check: this should never happen
      if (debugEnabled())
        TRACER.debugInfo("Received unexpected ack from server id: "
          + ackingServerId + " ack message: " + ackMsg);
        return false;
    } else
    {
      // Analyze received ack and update info for the ack to be later computed
      // accordingly
      boolean someErrors = false;
      if (ackMsg.hasTimeout())
      {
        hasTimeout = true;
        someErrors = true;
      }
      if (ackMsg.hasWrongStatus())
      {
        hasWrongStatus = true;
        someErrors = true;
      }
      if (ackMsg.hasReplayError())
      {
        hasReplayError = true;
        someErrors = true;
      }
      if (someErrors)
      {
        failedServers.addAll(ackMsg.getFailedServers());
      }
      // Mark this ack received for the server
      expectedServersAckStatus.put(ackingServerId, true);
      numKnownAckStatus++;
    }
    return (numKnownAckStatus == expectedServersAckStatus.size());
  }
  /**
   * {@inheritDoc}
   */
  public AckMsg createAck(boolean timeout)
  {
    AckMsg ack = new AckMsg(changeNumber);
    // Fill collected errors info
    ack.setHasTimeout(hasTimeout);
    ack.setHasWrongStatus(hasWrongStatus);
    ack.setHasReplayError(hasReplayError);
    ack.setFailedServers(failedServers);
    // Force anyway timeout flag if requested
    if (timeout)
      ack.setHasTimeout(true);
    return ack;
  }
}
opends/src/server/org/opends/server/replication/server/ServerHandler.java
@@ -40,7 +40,6 @@
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
@@ -67,7 +66,6 @@
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.Attributes;
import org.opends.server.types.DN;
import org.opends.server.types.InitializationException;
import org.opends.server.util.TimeThread;
@@ -95,16 +93,12 @@
  private ProtocolSession session;
  private final MsgQueue msgQueue = new MsgQueue();
  private MsgQueue lateQueue = new MsgQueue();
  private final Map<ChangeNumber, AckMessageList> waitingAcks =
    new HashMap<ChangeNumber, AckMessageList>();
  private ReplicationServerDomain replicationServerDomain = null;
  private String serverURL;
  private int outCount = 0; // number of update sent to the server
  private int inCount = 0;  // number of updates received from the server
  private int inAckCount = 0;
  private int outAckCount = 0;
  private int maxReceiveQueue = 0;
  private int maxSendQueue = 0;
  private int maxReceiveDelay = 0;
@@ -120,7 +114,7 @@
  private ServerState serverState;
  private boolean activeWriter = true;
  private ServerWriter writer = null;
  private DN baseDn = null;
  private String baseDn = null;
  private int rcvWindow;
  private int rcvWindowSizeHalf;
  private int maxRcvWindow;
@@ -143,7 +137,7 @@
   * Properties filled only if remote server is a DS
   */
  // Status of this 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>();
@@ -182,9 +176,6 @@
   * Set when ServerHandler is stopping.
   */
  private AtomicBoolean shuttingDown = new AtomicBoolean(false);
  private static final Map<ChangeNumber, ReplServerAckMessageList>
    changelogsWaitingAcks =
    new HashMap<ChangeNumber, ReplServerAckMessageList>();
  /**
   * Creates a new server handler instance with the provided socket.
@@ -266,7 +257,7 @@
   * @param replicationServer the ReplicationServer that created this server
   *                          handler.
   */
  public void start(DN baseDn, short replicationServerId,
  public void start(String baseDn, short replicationServerId,
    String replicationServerURL,
    int windowSize, boolean sslEncryption,
    ReplicationServer replicationServer)
@@ -571,7 +562,7 @@
              {
                // Timeout
                Message message = NOTE_TIMEOUT_WHEN_CROSS_CONNECTION.get(
                  this.baseDn.toNormalizedString(),
                  this.baseDn,
                  Short.toString(serverId),
                  Short.toString(replicationServer.getServerId()));
                closeSession(message);
@@ -732,7 +723,7 @@
                    //     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.toNormalizedString(),
                      this.baseDn,
                      Short.toString(serverId),
                      Long.toString(generationId),
                      Long.toString(localGenerationId));
@@ -759,7 +750,7 @@
                    // replicationServerDomain.
                    // setGenerationId(generationId, false);
                    Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
                      this.baseDn.toNormalizedString(),
                      this.baseDn,
                      Short.toString(serverId),
                      Long.toString(generationId),
                      Long.toString(localGenerationId));
@@ -859,7 +850,7 @@
            if (generationId != localGenerationId)
            {
              Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get(
                this.baseDn.toNormalizedString(),
                this.baseDn,
                Short.toString(serverId),
                Long.toString(generationId),
                Long.toString(localGenerationId));
@@ -873,7 +864,7 @@
              // 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.toNormalizedString(),
                this.baseDn,
                Short.toString(serverId),
                Long.toString(generationId),
                Long.toString(localGenerationId));
@@ -957,7 +948,7 @@
                  //     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.toNormalizedString(),
                    this.baseDn,
                    Short.toString(serverId),
                    Long.toString(generationId),
                    Long.toString(localGenerationId));
@@ -984,7 +975,7 @@
                  // replicationServerDomain.
                  // setGenerationId(generationId, false);
                  Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
                    this.baseDn.toNormalizedString(),
                    this.baseDn,
                    Short.toString(serverId),
                    Long.toString(generationId),
                    Long.toString(localGenerationId));
@@ -1195,26 +1186,6 @@
  }
  /**
   * Get the number of Ack received from the server managed by this handler.
   *
   * @return Returns the inAckCount.
   */
  public int getInAckCount()
  {
    return inAckCount;
  }
  /**
   * Get the number of Ack sent to the server managed by this handler.
   *
   * @return Returns the outAckCount.
   */
  public int getOutAckCount()
  {
    return outAckCount;
  }
  /**
   * 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
@@ -1763,145 +1734,14 @@
  }
  /**
   * Send the ack to the server that did the original modification.
   * Sends an ack message to the server represented by this object.
   *
   * @param changeNumber The ChangeNumber of the update that is acked.
   * @param ack The ack message to be sent.
   * @throws IOException In case of Exception thrown sending the ack.
   */
  public void sendAck(ChangeNumber changeNumber) throws IOException
  public void sendAck(AckMsg ack) throws IOException
  {
    AckMsg ack = new AckMsg(changeNumber);
    session.publish(ack);
    outAckCount++;
  }
  /**
   * Do the work when an ack message has been received from another server.
   *
   * @param message The ack message that was received.
   * @param ackingServerId The  id of the server that acked the change.
   */
  public void ack(AckMsg message, short ackingServerId)
  {
    ChangeNumber changeNumber = message.getChangeNumber();
    AckMessageList ackList;
    boolean completedFlag;
    synchronized (waitingAcks)
    {
      ackList = waitingAcks.get(changeNumber);
      if (ackList == null)
        return;
      ackList.addAck(ackingServerId);
      completedFlag = ackList.completed();
      if (completedFlag)
      {
        waitingAcks.remove(changeNumber);
      }
    }
    if (completedFlag)
    {
      replicationServerDomain.sendAck(changeNumber, true);
    }
  }
  /**
   * Process reception of an for an update that was received from a
   * ReplicationServer.
   *
   * @param message the ack message that was received.
   * @param ackingServerId The  id of the server that acked the change.
   */
  public static void ackChangelog(AckMsg message, short ackingServerId)
  {
    ChangeNumber changeNumber = message.getChangeNumber();
    ReplServerAckMessageList ackList;
    boolean completedFlag;
    synchronized (changelogsWaitingAcks)
    {
      ackList = changelogsWaitingAcks.get(changeNumber);
      if (ackList == null)
        return;
      ackList.addAck(ackingServerId);
      completedFlag = ackList.completed();
      if (completedFlag)
      {
        changelogsWaitingAcks.remove(changeNumber);
      }
    }
    if (completedFlag)
    {
      ReplicationServerDomain replicationServerDomain =
        ackList.getChangelogCache();
      replicationServerDomain.sendAck(changeNumber, false,
        ackList.getReplicationServerId());
    }
  }
  /**
   * Add an update to the list of update waiting for acks.
   *
   * @param update the update that must be added to the list
   * @param nbWaitedAck  The number of ack that must be received before
   *               the update is fully acked.
   */
  public void addWaitingAck(UpdateMsg update, int nbWaitedAck)
  {
    AckMessageList ackList = new AckMessageList(update.getChangeNumber(),
      nbWaitedAck);
    synchronized (waitingAcks)
    {
      waitingAcks.put(update.getChangeNumber(), ackList);
    }
  }
  /**
   * Add an update to the list of update received from a replicationServer and
   * waiting for acks.
   *
   * @param update The update that must be added to the list.
   * @param ChangelogServerId The identifier of the replicationServer that sent
   *                          the update.
   * @param replicationServerDomain The ReplicationServerDomain from which the
   *                                change was processed and to which the ack
   *                                must later be sent.
   * @param nbWaitedAck The number of ack that must be received before
   *                    the update is fully acked.
   */
  public static void addWaitingAck(
    UpdateMsg update,
    short ChangelogServerId, ReplicationServerDomain replicationServerDomain,
    int nbWaitedAck)
  {
    ReplServerAckMessageList ackList =
      new ReplServerAckMessageList(update.getChangeNumber(),
      nbWaitedAck,
      ChangelogServerId,
      replicationServerDomain);
    synchronized (changelogsWaitingAcks)
    {
      changelogsWaitingAcks.put(update.getChangeNumber(), ackList);
    }
  }
  /**
   * Get the size of the list of update waiting for acks.
   *
   * @return the size of the list of update waiting for acks.
   */
  public int getWaitingAckSize()
  {
    synchronized (waitingAcks)
    {
      return waitingAcks.size();
    }
  }
  /**
   * Increment the count of Acks received from this server.
   */
  public void incrementInAckCount()
  {
    inAckCount++;
  }
  /**
@@ -2001,13 +1841,13 @@
        .valueOf(serverId)));
    attributes.add(Attributes.create("base-dn", baseDn.toString()));
    if (serverIsLDAPserver)
    try
    {
      MonitorData md;
      try
      {
        md = replicationServerDomain.getMonitorData();
      md = replicationServerDomain.getMonitorData();
      if (serverIsLDAPserver)
      {
        // Oldest missing update
        Long approxFirstMissingDate = md.getApproxFirstMissingDate(serverId);
        if ((approxFirstMissingDate != null) && (approxFirstMissingDate > 0))
@@ -2017,7 +1857,7 @@
              "approx-older-change-not-synchronized", date.toString()));
          attributes.add(Attributes.create(
              "approx-older-change-not-synchronized-millis", String
                  .valueOf(approxFirstMissingDate)));
              .valueOf(approxFirstMissingDate)));
        }
        // Missing changes
@@ -2030,14 +1870,21 @@
        attributes.add(Attributes.create("approximate-delay", String
            .valueOf(delay)));
      }
      catch (Exception e)
      else
      {
        // TODO: improve the log
        // We failed retrieving the remote monitor data.
        attributes.add(Attributes.create("error",
            stackTraceToSingleLineString(e)));
        // Missing changes
        long missingChanges = md.getMissingChangesRS(serverId);
        attributes.add(Attributes.create("missing-changes", String
            .valueOf(missingChanges)));
      }
    }
    catch (Exception e)
    {
      // TODO: improve the log
      // We failed retrieving the remote monitor data.
      attributes.add(Attributes.create("error",
          stackTraceToSingleLineString(e)));
    }
    attributes.add(
        Attributes.create("queue-size", String.valueOf(msgQueue.count())));
@@ -2056,14 +1903,6 @@
    attributes.add(Attributes.create("update-received", String
        .valueOf(getInCount())));
    // Deprecated as long as assured is not exposed
    attributes.add(Attributes.create("update-waiting-acks", String
        .valueOf(getWaitingAckSize())));
    attributes.add(Attributes.create("ack-sent", String
        .valueOf(getOutAckCount())));
    attributes.add(Attributes.create("ack-received", String
        .valueOf(getInAckCount())));
    // Window stats
    attributes.add(Attributes.create("max-send-window", String
        .valueOf(sendWindowSize)));
@@ -2125,11 +1964,14 @@
    /*
     * Stop the remote LSHandler
     */
    for (LightweightServerHandler lsh : directoryServers.values())
    synchronized (directoryServers)
    {
      lsh.stopHandler();
      for (LightweightServerHandler lsh : directoryServers.values())
      {
        lsh.stopHandler();
      }
      directoryServers.clear();
    }
    directoryServers.clear();
    /*
     * Stop the heartbeat thread.
@@ -2217,7 +2059,6 @@
      {
        WindowMsg msg = new WindowMsg(rcvWindowSizeHalf);
        session.publish(msg);
        outAckCount++;
        rcvWindow += rcvWindowSizeHalf;
      }
    }
@@ -2302,23 +2143,26 @@
     */
    List<DSInfo> dsInfos = topoMsg.getDsList();
    // Removes the existing structures
    for (LightweightServerHandler lsh : directoryServers.values())
    synchronized (directoryServers)
    {
      lsh.stopHandler();
    }
    directoryServers.clear();
      // 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);
      // 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);
      }
    }
  }
@@ -2445,7 +2289,10 @@
   */
  public boolean hasRemoteLDAPServers()
  {
    return !directoryServers.isEmpty();
    synchronized (directoryServers)
    {
      return !directoryServers.isEmpty();
    }
  }
  /**
@@ -2496,7 +2343,6 @@
      // TODO also log an error message.
      WindowMsg msg = new WindowMsg(rcvWindow);
      session.publish(msg);
      outAckCount++;
    } else
    {
      // Both the LDAP server and the replication server believes that the
@@ -2556,17 +2402,10 @@
   */
  public Set<Short> getConnectedDirectoryServerIds()
  {
    return directoryServers.keySet();
  }
  /**
   * Get the map of connected DSs
   * (to the RS represented by this server handler).
   * @return The map of connected DSs
   */
  public Map<Short, LightweightServerHandler> getConnectedDSs()
  {
    return directoryServers;
    synchronized (directoryServers)
    {
      return directoryServers.keySet();
    }
  }
  /**
@@ -2716,4 +2555,32 @@
  {
    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)
    {
      for (LightweightServerHandler ls : directoryServers.values())
      {
        dsInfos.add(ls.toDSInfo());
      }
    }
  }
  /**
   * Gets the group id of the server represented by this object.
   * @return The group id of the server represented by this object.
   */
  public byte getGroupId()
  {
    return groupId;
  }
}
opends/src/server/org/opends/server/replication/server/ServerReader.java
@@ -141,7 +141,7 @@
          {
            AckMsg ack = (AckMsg) msg;
            handler.checkWindow();
            replicationServerDomain.ack(ack, serverId);
            replicationServerDomain.processAck(ack, handler);
          } else if (msg instanceof UpdateMsg)
          {
            boolean filtered = false;
@@ -171,7 +171,7 @@
                  logError(ERR_IGNORING_UPDATE_FROM_DS_BADGENID.get(
                    Short.toString(replicationServerDomain.
                    getReplicationServer().getServerId()),
                    replicationServerDomain.getBaseDn().toNormalizedString(),
                    replicationServerDomain.getBaseDn(),
                    ((UpdateMsg) msg).getChangeNumber().toString(),
                    Short.toString(handler.getServerId()),
                    Long.toString(referenceGenerationId),
@@ -180,7 +180,7 @@
                  logError(ERR_IGNORING_UPDATE_FROM_DS_FULLUP.get(
                    Short.toString(replicationServerDomain.
                    getReplicationServer().getServerId()),
                    replicationServerDomain.getBaseDn().toNormalizedString(),
                    replicationServerDomain.getBaseDn(),
                    ((UpdateMsg) msg).getChangeNumber().toString(),
                    Short.toString(handler.getServerId())));
                filtered = true;
@@ -199,7 +199,7 @@
                logError(ERR_IGNORING_UPDATE_FROM_RS.get(
                  Short.toString(replicationServerDomain.getReplicationServer().
                  getServerId()),
                  replicationServerDomain.getBaseDn().toNormalizedString(),
                  replicationServerDomain.getBaseDn(),
                  ((UpdateMsg) msg).getChangeNumber().toString(),
                  Short.toString(handler.getServerId()),
                  Long.toString(referenceGenerationId),
opends/src/server/org/opends/server/replication/server/ServerWriter.java
@@ -138,7 +138,7 @@
              logError(ERR_IGNORING_UPDATE_TO_DS_BADGENID.get(
                Short.toString(replicationServerDomain.getReplicationServer().
                getServerId()),
                replicationServerDomain.getBaseDn().toNormalizedString(),
                replicationServerDomain.getBaseDn(),
                update.getChangeNumber().toString(),
                Short.toString(handler.getServerId()),
                Long.toString(handler.getGenerationId()),
@@ -147,7 +147,7 @@
              logError(ERR_IGNORING_UPDATE_TO_DS_FULLUP.get(
                Short.toString(replicationServerDomain.getReplicationServer().
                getServerId()),
                replicationServerDomain.getBaseDn().toNormalizedString(),
                replicationServerDomain.getBaseDn(),
                update.getChangeNumber().toString(),
                Short.toString(handler.getServerId())));
            continue;
@@ -166,7 +166,7 @@
            logError(ERR_IGNORING_UPDATE_TO_RS.get(
              Short.toString(replicationServerDomain.getReplicationServer().
              getServerId()),
              replicationServerDomain.getBaseDn().toNormalizedString(),
              replicationServerDomain.getBaseDn(),
              update.getChangeNumber().toString(),
              Short.toString(handler.getServerId()),
              Long.toString(handler.getGenerationId()),
opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java
@@ -75,7 +75,7 @@
    int degradedStatusThreshold)
  {
    super("Replication Server Status Analyzer for " +
      replicationServerDomain.getBaseDn().toNormalizedString() + " in RS " +
      replicationServerDomain.getBaseDn() + " in RS " +
      replicationServerDomain.getReplicationServer().getServerId());
    this.replicationServerDomain = replicationServerDomain;
opends/src/server/org/opends/server/replication/service/InitializeTargetTask.java
File was renamed from opends/src/server/org/opends/server/tasks/InitializeTargetTask.java
@@ -24,7 +24,12 @@
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.tasks;
package org.opends.server.replication.service;
import org.opends.server.tasks.TaskUtils;
import org.opends.messages.TaskMessages;
import org.opends.server.types.ResultCode;
import org.opends.messages.MessageBuilder;
import static org.opends.server.config.ConfigConstants.*;
@@ -36,18 +41,12 @@
import org.opends.server.backends.task.Task;
import org.opends.server.backends.task.TaskState;
import org.opends.messages.TaskMessages;
import org.opends.messages.Message;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
/**
 * This class provides an implementation of a Directory Server task that can
 * be used to import data from an LDIF file into a backend.
@@ -60,22 +59,10 @@
  private static final DebugTracer TRACER = getTracer();
  // Config properties
  boolean append                  = false;
  boolean isCompressed            = false;
  boolean isEncrypted             = false;
  boolean skipSchemaValidation    = false;
  String  domainString            = null;
  ReplicationDomain domain = null;
  short target;
  long total;
  long left;
  /**
   * {@inheritDoc}
   */
  public Message getDisplayName() {
    return TaskMessages.INFO_TASK_INITIALIZE_TARGET_NAME.get();
  }
  private String  domainString            = null;
  private ReplicationDomain domain = null;
  private short target;
  private long total;
  /**
   * {@inheritDoc}
@@ -102,20 +89,17 @@
    attrList = taskEntry.getAttribute(typeDomainBase);
    domainString = TaskUtils.getSingleValueString(attrList);
    DN domainDN = DN.nullDN();
    try
    {
      domainDN = DN.decode(domainString);
      domain = ReplicationDomain.retrievesReplicationDomain(domainString);
    }
    catch(Exception e)
    catch(DirectoryException e)
    {
      MessageBuilder mb = new MessageBuilder();
      mb.append(TaskMessages.ERR_TASK_INITIALIZE_INVALID_DN.get());
      mb.append(e.getLocalizedMessage());
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
              mb.toMessage());
      mb.append(e.getMessage());
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, e);
    }
    domain=ReplicationDomain.retrievesReplicationDomain(domainDN);
    attrList = taskEntry.getAttribute(typeScope);
    String targetString = TaskUtils.getSingleValueString(attrList);
@@ -170,7 +154,6 @@
   */
  public void setLeft(long left)  throws DirectoryException
  {
    this.left = left;
    replaceAttributeValue(ATTR_TASK_INITIALIZE_LEFT, String.valueOf(left));
    replaceAttributeValue(ATTR_TASK_INITIALIZE_DONE,String.valueOf(total-left));
  }
opends/src/server/org/opends/server/replication/service/InitializeTask.java
File was renamed from opends/src/server/org/opends/server/tasks/InitializeTask.java
@@ -24,10 +24,16 @@
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.tasks;
import org.opends.messages.Message;
package org.opends.server.replication.service;
import org.opends.server.tasks.TaskUtils;
import org.opends.server.types.ResultCode;
import org.opends.messages.MessageBuilder;
import org.opends.messages.Message;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.core.DirectoryServer.getAttributeType;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
@@ -39,13 +45,10 @@
import org.opends.server.backends.task.Task;
import org.opends.server.backends.task.TaskState;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
/**
 * This class provides an implementation of a Directory Server task that can
@@ -59,13 +62,10 @@
   */
  private static final DebugTracer TRACER = getTracer();
  boolean isCompressed            = false;
  boolean isEncrypted             = false;
  boolean skipSchemaValidation    = false;
  String  domainString            = null;
  short  source;
  ReplicationDomain domain = null;
  TaskState initState;
  private String  domainString            = null;
  private short  source;
  private ReplicationDomain domain        = null;
  private TaskState initState;
  // The total number of entries expected to be processed when this import
  // will end successfully
@@ -108,22 +108,19 @@
    List<Attribute> attrList;
    attrList = taskEntry.getAttribute(typeDomainBase);
    domainString = TaskUtils.getSingleValueString(attrList);
    DN domainDN = DN.nullDN();
    try
    {
      domainDN = DN.decode(domainString);
      domain = ReplicationDomain.retrievesReplicationDomain(domainString);
    }
    catch(Exception e)
    catch(DirectoryException e)
    {
      MessageBuilder mb = new MessageBuilder();
      mb.append(TaskMessages.ERR_TASK_INITIALIZE_INVALID_DN.get());
      mb.append(e.getMessage());
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
          mb.toMessage());
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, e);
    }
    domain = ReplicationDomain.retrievesReplicationDomain(domainDN);
    attrList = taskEntry.getAttribute(typeSourceScope);
    String sourceString = TaskUtils.getSingleValueString(attrList);
    source = domain.decodeSource(sourceString);
@@ -140,7 +137,7 @@
    if (debugEnabled())
    {
      TRACER.debugInfo("InitializeTask is starting domain: %s source:%d",
                domain.getBaseDN(), source);
                domain.getServiceID(), source);
    }
    initState = getTaskState();
    try
opends/src/server/org/opends/server/replication/service/ListenerThread.java
File was renamed from opends/src/server/org/opends/server/replication/plugin/ListenerThread.java
@@ -24,9 +24,7 @@
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
package org.opends.server.replication.service;
import org.opends.messages.Message;
import static org.opends.server.loggers.ErrorLogger.logError;
@@ -53,23 +51,18 @@
  private ReplicationDomain repDomain;
  private boolean shutdown = false;
  private boolean done = false;
  private LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue;
  /**
   * Constructor for the ListenerThread.
   *
   * @param repDomain the replication domain that created this thread
   * @param updateToReplayQueue The update messages queue we must
   * store messages in
   */
  public ListenerThread(ReplicationDomain repDomain,
    LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue)
  public ListenerThread(ReplicationDomain repDomain)
  {
     super("Replication Listener for server id " + repDomain.getServerId() +
         " and domain " + repDomain.getBaseDN());
         " and domain " + repDomain.getServiceID());
     this.repDomain = repDomain;
     this.updateToReplayQueue = updateToReplayQueue;
  }
  /**
@@ -101,24 +94,8 @@
        // queue
        while ((!shutdown) && ((updateMsg = repDomain.receive()) != null))
        {
          // Put update message into the queue (block until some place in the
          // queue is available)
          UpdateToReplay updateToReplay =
            new UpdateToReplay(updateMsg, repDomain);
          boolean queued = false;
          while (!queued && !shutdown)
          {
            // Use timedout method (offer) instead of put for being able to
            // shutdown the thread
            queued = updateToReplayQueue.offer(updateToReplay,
              1L, TimeUnit.SECONDS);
          }
          if (!queued)
          {
            // Shutdown requested but could not push message: ensure this one is
            // not lost and put it in the queue before dying
            updateToReplayQueue.offer(updateToReplay);
          }
          if (repDomain.processUpdate(updateMsg) == true)
            repDomain.processUpdateDoneSynchronous(updateMsg);
        }
        if (updateMsg == null)
          shutdown = true;
@@ -134,9 +111,6 @@
      }
    }
    // Stop the HeartBeat thread
    repDomain.getBroker().stopHeartBeat();
    done = true;
    if (debugEnabled())
@@ -161,7 +135,7 @@
        if (n >= FACTOR)
        {
          TRACER.debugInfo("Interrupting listener thread for dn " +
            repDomain.getBaseDN() + " in DS " + repDomain.getServerId());
            repDomain.getServiceID() + " in DS " + repDomain.getServerId());
          this.interrupt();
        }
      }
opends/src/server/org/opends/server/replication/service/ReplInputStream.java
File was renamed from opends/src/server/org/opends/server/replication/plugin/ReplLDIFInputStream.java
@@ -24,7 +24,7 @@
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
package org.opends.server.replication.service;
@@ -36,7 +36,7 @@
 * This class creates an input stream that can be used to read entries generated
 * by SynchroLDIF as if they were being read from another source like a file.
 */
public class ReplLDIFInputStream
public class ReplInputStream
extends InputStream
{
  // Indicates whether this input stream has been closed.
@@ -51,14 +51,14 @@
  /**
   * Creates a new ReplLDIFInputStream that will import entries
   * for a synchronzation domain.
   * for a synchronization domain.
   *
   * @param domain The replication domain
   */
  public ReplLDIFInputStream(ReplicationDomain domain)
  public ReplInputStream(ReplicationDomain domain)
  {
    this.domain = domain;
    closed       = false;
    closed      = false;
  }
  /**
opends/src/server/org/opends/server/replication/service/ReplOutputStream.java
New file
@@ -0,0 +1,82 @@
/*
 * 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-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.service;
import java.io.IOException;
import java.io.OutputStream;
/**
 * This class creates an output stream that can be used to export entries
 * to a synchronization domain.
 */
public class ReplOutputStream
       extends OutputStream
{
  // The synchronization domain on which the export is done
  ReplicationDomain domain;
  // The current number of entries exported
  private long numExportedEntries;
  String entryBuffer = "";
  /**
   * Creates a new ReplLDIFOutputStream related to a replication
   * domain.
   *
   * @param domain The replication domain
   */
  public ReplOutputStream(ReplicationDomain domain)
  {
    this.domain = domain;
  }
  /**
   * {@inheritDoc}
   */
  public void write(int i) throws IOException
  {
    throw new IOException("Invalid call");
  }
  /**
   * {@inheritDoc}
   */
  public void write(byte b[], int off, int len) throws IOException
  {
    domain.exportLDIFEntry(b, off, len);
  }
  /**
   * Return the number of exported entries.
   * @return the numExportedEntries
   */
  public long getNumExportedEntries() {
    return numExportedEntries;
  }
}
opends/src/server/org/opends/server/replication/service/ReplicationBroker.java
File was renamed from opends/src/server/org/opends/server/replication/plugin/ReplicationBroker.java
@@ -24,12 +24,15 @@
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
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.util.StaticUtils.stackTraceToSingleLineString;
@@ -45,34 +48,22 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.opends.server.api.DirectoryThread;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchListener;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPFilter;
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.types.DN;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.types.SearchScope;
/**
 * The broker for Multi-master Replication.
 */
public class ReplicationBroker implements InternalSearchListener
public class ReplicationBroker
{
  /**
@@ -83,23 +74,17 @@
  private Collection<String> servers;
  private boolean connected = false;
  private String replicationServer = "Not connected";
  private TreeSet<FakeOperation> replayOperations;
  private ProtocolSession session = null;
  private final ServerState state;
  private final DN baseDn;
  private final String baseDn;
  private final short serverId;
  private int maxSendDelay;
  private int maxReceiveDelay;
  private int maxSendQueue;
  private int maxReceiveQueue;
  private Semaphore sendWindow;
  private int maxSendWindow;
  private int rcvWindow;
  private int halfRcvWindow;
  private int maxRcvWindow;
  private int rcvWindow = 100;
  private int halfRcvWindow = rcvWindow/2;
  private int maxRcvWindow = rcvWindow;
  private int timeout = 0;
  private short protocolVersion;
  private long generationId = -1;
  private ReplSessionSecurity replSessionSecurity;
  // My group id
  private byte groupId = (byte) -1;
@@ -110,7 +95,7 @@
  // The server URL of the RS we are connected to
  private String rsServerUrl = null;
  // Our replication domain
  private ReplicationDomain replicationDomain = null;
  private ReplicationDomain domain = null;
  // Trick for avoiding a inner class for many parameters return for
  // performPhaseOneHandshake method.
@@ -142,6 +127,17 @@
  // Same group id poller thread
  private SameGroupIdPoller sameGroupIdPoller = null;
  /*
   * Properties for the last topology info received from the network.
   */
  // Info for other DSs.
  // Warning: does not contain info for us (for our server id)
  private List<DSInfo> dsList = new ArrayList<DSInfo>();
  // Info for other RSs.
  private List<RSInfo> rsList = new ArrayList<RSInfo>();
  private long generationID;
  /**
   * Creates a new ReplicationServer Broker for a particular ReplicationDomain.
   *
@@ -152,13 +148,6 @@
   *              when negotiating the session with the replicationServer.
   * @param serverId The server ID that should be used by this broker
   *              when negotiating the session with the replicationServer.
   * @param maxReceiveQueue The maximum size of the receive queue to use on
   *                         the replicationServer.
   * @param maxReceiveDelay The maximum replication delay to use on the
   *                        replicationServer.
   * @param maxSendQueue The maximum size of the send queue to use on
   *                     the replicationServer.
   * @param maxSendDelay The maximum send delay to use on the replicationServer.
   * @param window The size of the send and receive window to use.
   * @param heartbeatInterval The interval between heartbeats requested of the
   * replicationServer, or zero if no heartbeats are requested.
@@ -169,29 +158,32 @@
   * @param groupId The group id of our domain.
   */
  public ReplicationBroker(ReplicationDomain replicationDomain,
    ServerState state, DN baseDn, short serverId, int maxReceiveQueue,
    int maxReceiveDelay, int maxSendQueue, int maxSendDelay, int window,
    long heartbeatInterval, long generationId,
    ServerState state, String baseDn, short serverId, int window,
    long generationId, long heartbeatInterval,
    ReplSessionSecurity replSessionSecurity, byte groupId)
  {
    this.replicationDomain = replicationDomain;
    this.domain = replicationDomain;
    this.baseDn = baseDn;
    this.serverId = serverId;
    this.maxReceiveDelay = maxReceiveDelay;
    this.maxSendDelay = maxSendDelay;
    this.maxReceiveQueue = maxReceiveQueue;
    this.maxSendQueue = maxSendQueue;
    this.state = state;
    replayOperations =
      new TreeSet<FakeOperation>(new FakeOperationComparator());
    this.rcvWindow = window;
    this.maxRcvWindow = window;
    this.halfRcvWindow = window / 2;
    this.heartbeatInterval = heartbeatInterval;
    this.protocolVersion = ProtocolVersion.getCurrentVersion();
    this.generationId = generationId;
    this.replSessionSecurity = replSessionSecurity;
    this.groupId = groupId;
    this.generationID = generationId;
    this.heartbeatInterval = heartbeatInterval;
    this.maxRcvWindow = window;
    this.maxRcvWindow = window;
    this.halfRcvWindow = window /2;
  }
  /**
   * Start the ReplicationBroker.
   */
  public void start()
  {
    shutdown = false;
    this.rcvWindow = this.maxRcvWindow;
    this.connect();
  }
  /**
@@ -207,6 +199,7 @@
     */
    shutdown = false;
    this.servers = servers;
    if (servers.size() < 1)
    {
      Message message = NOTE_NEED_MORE_THAN_ONE_CHANGELOG_SERVER.get();
@@ -245,6 +238,18 @@
  }
  /**
   * Gets the server id.
   * @return The server id
   */
  private long getGenerationID()
  {
    if (domain != null)
      return domain.getGenerationID();
    else
      return generationID;
  }
  /**
   * Gets the server url of the RS we are connected to.
   * @return The server url of the RS we are connected to
   */
@@ -324,12 +329,12 @@
    // May have created a broker with null replication domain for
    // unit test purpose.
    if (replicationDomain != null)
    if (domain != null)
    {
      // If a first connect or a connection failure occur, we go through here.
      // force status machine to NOT_CONNECTED_STATUS so that monitoring can
      // see that we are not connected.
      replicationDomain.toNotConnectedStatus();
      domain.toNotConnectedStatus();
    }
    // Stop any existing poller and heartbeat monitor from a previous session.
@@ -380,7 +385,8 @@
          ServerStatus initStatus =
            computeInitialServerStatus(replServerStartMsg.getGenerationId(),
            bestServerInfo.getServerState(),
            replServerStartMsg.getDegradedStatusThreshold(), generationId);
            replServerStartMsg.getDegradedStatusThreshold(),
            this.getGenerationID());
          // Perfom session start (handshake phase 2)
          TopologyMsg topologyMsg = performPhaseTwoHandshake(bestServer,
@@ -414,81 +420,6 @@
              if ((tmpRsGroupId == groupId) ||
                ((tmpRsGroupId != groupId) && !someServersWithSameGroupId))
              {
                /*
                 * We must not publish changes to a replicationServer that has
                 * not seen all our previous changes because this could cause
                 * some other ldap servers to miss those changes.
                 * Check that the ReplicationServer has seen all our previous
                 * changes.
                 */
                ChangeNumber replServerMaxChangeNumber =
                  replServerStartMsg.getServerState().
                  getMaxChangeNumber(serverId);
                if (replServerMaxChangeNumber == null)
                {
                  replServerMaxChangeNumber = new ChangeNumber(0, 0, serverId);
                }
                ChangeNumber ourMaxChangeNumber =
                  state.getMaxChangeNumber(serverId);
                if ((ourMaxChangeNumber != null) &&
                  (!ourMaxChangeNumber.olderOrEqual(replServerMaxChangeNumber)))
                {
                  // Replication server is missing some of our changes: let's
                  // send them to him.
                  Message message = DEBUG_GOING_TO_SEARCH_FOR_CHANGES.get();
                  logError(message);
                  /*
                   * Get all the changes that have not been seen by this
                   * replication server and populate the replayOperations
                   * list.
                   */
                  InternalSearchOperation op = searchForChangedEntries(
                    baseDn, replServerMaxChangeNumber, this);
                  if (op.getResultCode() != ResultCode.SUCCESS)
                  {
                    /*
                     * An error happened trying to search for the updates
                     * This server will start acepting again new updates but
                     * some inconsistencies will stay between servers.
                     * Log an error for the repair tool
                     * that will need to resynchronize the servers.
                     */
                    message = ERR_CANNOT_RECOVER_CHANGES.get(
                      baseDn.toNormalizedString());
                    logError(message);
                  } else
                  {
                    for (FakeOperation replayOp : replayOperations)
                    {
                      ChangeNumber cn = replayOp.getChangeNumber();
                      /*
                       * Because the entry returned by the search operation
                       * can contain old historical information, it is
                       * possible that some of the FakeOperation are
                       * actually older than the
                       * Only send the Operation if it was newer than
                       * the last ChangeNumber known by the Replication Server.
                       */
                      if (cn.newer(replServerMaxChangeNumber))
                      {
                        message =
                          DEBUG_SENDING_CHANGE.get(
                              replayOp.getChangeNumber().toString());
                        logError(message);
                        session.publish(replayOp.generateMessage());
                      }
                    }
                    message = DEBUG_CHANGES_SENT.get();
                    logError(message);
                  }
                  replayOperations.clear();
                }
                replicationServer = tmpReadableServerName;
                maxSendWindow = replServerStartMsg.getWindowSize();
                rsGroupId = replServerStartMsg.getGroupId();
@@ -497,11 +428,13 @@
                // May have created a broker with null replication domain for
                // unit test purpose.
                if (replicationDomain != null)
                if (domain != null)
                {
                  replicationDomain.setInitialStatus(initStatus);
                  replicationDomain.receiveTopo(topologyMsg);
                  domain.sessionInitiated(
                      initStatus, replServerStartMsg.getServerState(),
                      session);
                }
                receiveTopo(topologyMsg);
                connected = true;
                if (getRsGroupId() != groupId)
                {
@@ -528,16 +461,10 @@
                // Do not log connection error
                newServerWithSameGroupId = true;
              }
            } catch (IOException e)
            {
              Message message = ERR_PUBLISHING_FAKE_OPS.get(
                baseDn.toNormalizedString(), bestServer,
                e.getLocalizedMessage() + stackTraceToSingleLineString(e));
              logError(message);
            } catch (Exception e)
            {
              Message message = ERR_COMPUTING_FAKE_OPS.get(
                baseDn.toNormalizedString(), bestServer,
                baseDn, bestServer,
                e.getLocalizedMessage() + stackTraceToSingleLineString(e));
              logError(message);
            } finally
@@ -577,7 +504,7 @@
        this.sendWindow = new Semaphore(maxSendWindow);
        connectPhaseLock.notify();
        if ((replServerStartMsg.getGenerationId() == this.generationId) ||
        if ((replServerStartMsg.getGenerationId() == this.getGenerationID()) ||
          (replServerStartMsg.getGenerationId() == -1))
        {
          Message message =
@@ -586,7 +513,7 @@
            Short.toString(rsServerId),
            replicationServer,
            Short.toString(serverId),
            Long.toString(this.generationId));
            Long.toString(this.getGenerationID()));
          logError(message);
        } else
        {
@@ -594,7 +521,7 @@
            NOTE_NOW_FOUND_BAD_GENERATION_CHANGELOG.get(
            baseDn.toString(),
            replicationServer,
            Long.toString(this.generationId),
            Long.toString(this.getGenerationID()),
            Long.toString(replServerStartMsg.getGenerationId()));
          logError(message);
        }
@@ -664,7 +591,7 @@
        if (debugEnabled())
        {
          TRACER.debugInfo("RB for dn " + baseDn.toNormalizedString() +
          TRACER.debugInfo("RB for dn " + baseDn +
            " and with server id " + Short.toString(serverId) + " computed " +
            Integer.toString(nChanges) + " changes late.");
        }
@@ -744,10 +671,11 @@
      /*
       * Send our ServerStartMsg.
       */
      ServerStartMsg serverStartMsg = new ServerStartMsg(serverId, baseDn,
        maxReceiveDelay, maxReceiveQueue, maxSendDelay, maxSendQueue,
        halfRcvWindow * 2, heartbeatInterval, state,
        ProtocolVersion.getCurrentVersion(), generationId, isSslEncryption,
      ServerStartMsg serverStartMsg = new ServerStartMsg(serverId,
          baseDn, 0, 0, 0, 0,
        maxRcvWindow, heartbeatInterval, state,
        ProtocolVersion.getCurrentVersion(), this.getGenerationID(),
        isSslEncryption,
        groupId);
      localSession.publish(serverStartMsg);
@@ -764,11 +692,11 @@
      }
      // Sanity check
      DN repDn = replServerStartMsg.getBaseDn();
      String repDn = replServerStartMsg.getBaseDn();
      if (!(this.baseDn.equals(repDn)))
      {
        Message message = ERR_DS_DN_DOES_NOT_MATCH.get(repDn.toString(),
          this.baseDn.toString());
          this.baseDn);
        logError(message);
        error = true;
      }
@@ -811,10 +739,10 @@
      if ( (e instanceof SocketTimeoutException) && debugEnabled() )
      {
        TRACER.debugInfo("Timeout trying to connect to RS " + server +
          " for dn: " + baseDn.toNormalizedString());
          " for dn: " + baseDn);
      }
      Message message = ERR_EXCEPTION_STARTING_SESSION_PHASE.get("1",
        baseDn.toNormalizedString(), server, e.getLocalizedMessage() +
        baseDn, server, e.getLocalizedMessage() +
        stackTraceToSingleLineString(e));
      if (keepConnection) // Log error message only for final connection
      {
@@ -880,13 +808,15 @@
      StartSessionMsg startSessionMsg = null;
      // May have created a broker with null replication domain for
      // unit test purpose.
      if (replicationDomain != null)
      if (domain != null)
      {
        startSessionMsg = new StartSessionMsg(initStatus,
          replicationDomain.getRefUrls(),
          replicationDomain.isAssured(),
          replicationDomain.getAssuredMode(),
          replicationDomain.getAssuredSdLevel());
        startSessionMsg =
          new StartSessionMsg(
              initStatus,
              domain.getRefUrls(),
              domain.isAssured(),
              domain.getAssuredMode(),
              domain.getAssuredSdLevel());
      } else
      {
        startSessionMsg =
@@ -912,7 +842,7 @@
    } catch (Exception e)
    {
      Message message = ERR_EXCEPTION_STARTING_SESSION_PHASE.get("2",
        baseDn.toNormalizedString(), server, e.getLocalizedMessage() +
        baseDn, server, e.getLocalizedMessage() +
        stackTraceToSingleLineString(e));
      logError(message);
@@ -950,7 +880,7 @@
   * @return The computed best replication server.
   */
  public static String computeBestReplicationServer(ServerState myState,
    HashMap<String, ServerInfo> rsInfos, short serverId, DN baseDn,
    HashMap<String, ServerInfo> rsInfos, short serverId, String baseDn,
    byte groupId)
  {
    /*
@@ -997,7 +927,7 @@
   * @return The computed best replication server.
   */
  private static String searchForBestReplicationServer(ServerState myState,
    HashMap<String, ServerInfo> rsInfos, short serverId, DN baseDn)
    HashMap<String, ServerInfo> rsInfos, short serverId, String baseDn)
  {
    /*
     * Find replication servers who are up to date (or more up to date than us,
@@ -1072,9 +1002,7 @@
       */
      Message message = NOTE_FOUND_CHANGELOGS_WITH_MY_CHANGES.get(
        upToDateServers.size(),
        baseDn.toNormalizedString(),
        Short.toString(serverId));
        upToDateServers.size(), baseDn, Short.toString(serverId));
      logError(message);
      /*
@@ -1154,7 +1082,7 @@
       */
      // lateOnes cannot be empty
      Message message = NOTE_COULD_NOT_FIND_CHANGELOG_WITH_MY_CHANGES.get(
        baseDn.toNormalizedString(), lateOnes.size());
        baseDn, lateOnes.size());
      logError(message);
      // Min of the shifts
@@ -1190,48 +1118,6 @@
  }
  /**
   * Search for the changes that happened since fromChangeNumber
   * based on the historical attribute. The only changes that will
   * be send will be the one generated on the serverId provided in
   * fromChangeNumber.
   * @param baseDn the base DN
   * @param fromChangeNumber The change number from which we want the changes
   * @param resultListener that will process the entries returned.
   * @return the internal search operation
   * @throws Exception when raised.
   */
  public static InternalSearchOperation searchForChangedEntries(
    DN baseDn,
    ChangeNumber fromChangeNumber,
    InternalSearchListener resultListener)
    throws Exception
  {
    InternalClientConnection conn =
      InternalClientConnection.getRootConnection();
    Short serverId = fromChangeNumber.getServerId();
    String maxValueForId = "ffffffffffffffff" +
      String.format("%04x", serverId) + "ffffffff";
    LDAPFilter filter = LDAPFilter.decode(
       "(&(" + Historical.HISTORICALATTRIBUTENAME + ">=dummy:"
       + fromChangeNumber + ")(" + Historical.HISTORICALATTRIBUTENAME +
       "<=dummy:" + maxValueForId + "))");
    LinkedHashSet<String> attrs = new LinkedHashSet<String>(1);
    attrs.add(Historical.HISTORICALATTRIBUTENAME);
    attrs.add(Historical.ENTRYUIDNAME);
    attrs.add("*");
    return conn.processSearch(
      new ASN1OctetString(baseDn.toString()),
      SearchScope.WHOLE_SUBTREE,
      DereferencePolicy.NEVER_DEREF_ALIASES,
      0, 0, false, filter,
      attrs,
      resultListener);
  }
  /**
   * Start the heartbeat monitor thread.
   */
  private void startHeartBeat()
@@ -1325,7 +1211,7 @@
      {
        MessageBuilder mb = new MessageBuilder();
        mb.append(NOTE_EXCEPTION_RESTARTING_SESSION.get(
          baseDn.toNormalizedString(), e.getLocalizedMessage()));
          baseDn, e.getLocalizedMessage()));
        mb.append(stackTraceToSingleLineString(e));
        logError(mb.toMessage());
      }
@@ -1413,7 +1299,7 @@
            }
          }
        }
        if (!credit)
        if ((!credit) && (currentWindowSemaphore.availablePermits() == 0))
        {
          // the window is still closed.
          // Send a WindowProbeMsg message to wakeup the receiver in case the
@@ -1436,7 +1322,7 @@
            if (debugEnabled())
            {
              debugInfo("ReplicationBroker.publish() " +
                "IO exception raised : " + e.getLocalizedMessage());
                "Interrupted exception raised : " + e.getLocalizedMessage());
            }
          }
        }
@@ -1479,7 +1365,13 @@
        {
          WindowMsg windowMsg = (WindowMsg) msg;
          sendWindow.release(windowMsg.getNumAck());
        } else
        }
        else if (msg instanceof TopologyMsg)
        {
          TopologyMsg topoMsg = (TopologyMsg)msg;
          receiveTopo(topoMsg);
        }
        else
        {
          return msg;
        }
@@ -1580,20 +1472,6 @@
  }
  /**
   * Set the value of the generationId for that broker. Normally the
   * generationId is set through the constructor but there are cases
   * where the value of the generationId must be changed while the broker
   * already exist for example after an on-line import.
   *
   * @param generationId The value of the generationId.
   *
   */
  public void setGenerationId(long generationId)
  {
    this.generationId = generationId;
  }
  /**
   * Get the name of the replicationServer to which this broker is currently
   * connected.
   *
@@ -1606,39 +1484,6 @@
  }
  /**
   * {@inheritDoc}
   */
  public void handleInternalSearchEntry(
    InternalSearchOperation searchOperation,
    SearchResultEntry searchEntry)
  {
    /*
     * This call back is called at session establishment phase
     * for each entry that has been changed by this server and the changes
     * have not been sent to any Replication Server.
     * The role of this method is to build equivalent operation from
     * the historical information and add them in the replayOperations
     * table.
     */
    Iterable<FakeOperation> updates =
      Historical.generateFakeOperations(searchEntry);
    for (FakeOperation op : updates)
    {
      replayOperations.add(op);
    }
  }
  /**
   * {@inheritDoc}
   */
  public void handleInternalSearchReference(
    InternalSearchOperation searchOperation,
    SearchResultReference searchReference)
  {
    // TODO to be implemented
  }
  /**
   * Get the maximum receive window size.
   *
   * @return The maximum receive window size.
@@ -1694,31 +1539,41 @@
  }
  /**
   * Change some config parameters.
   * Change some configuration parameters.
   *
   * @param replicationServers    The new list of replication servers.
   * @param maxReceiveQueue     The max size of receive queue.
   * @param maxReceiveDelay     The max receive delay.
   * @param maxSendQueue        The max send queue.
   * @param maxSendDelay        The max Send Delay.
   * @param replicationServers  The new list of replication servers.
   * @param window              The max window size.
   * @param heartbeatInterval   The heartbeat interval.
   * @param heartbeatInterval   The heartBeat interval.
   *
   * @return                    A boolean indicating if the changes
   *                            requires to restart the service.
   */
  public void changeConfig(Collection<String> replicationServers,
    int maxReceiveQueue, int maxReceiveDelay, int maxSendQueue,
    int maxSendDelay, int window, long heartbeatInterval)
  public boolean changeConfig(
      Collection<String> replicationServers, int window, long heartbeatInterval)
  {
    this.servers = replicationServers;
    this.maxRcvWindow = window;
    this.heartbeatInterval = heartbeatInterval;
    this.maxReceiveDelay = maxReceiveDelay;
    this.maxReceiveQueue = maxReceiveQueue;
    this.maxSendDelay = maxSendDelay;
    this.maxSendQueue = maxSendQueue;
    // These parameters needs to be renegociated with the ReplicationServer
    // so if they have changed, that requires restarting the session with
    // the ReplicationServer.
    Boolean needToRestartSession = false;
    // For info, a new session with the replicationServer
    // will be recreated in the replication domain
    // to take into account the new configuration.
    // A new session is necessary only when information regarding
    // the connection is modified
    if ((servers == null) ||
        (!(replicationServers.size() == servers.size()
        && replicationServers.containsAll(servers))) ||
        window != this.maxRcvWindow  ||
        heartbeatInterval != this.heartbeatInterval)
    {
      needToRestartSession = true;
    }
    this.servers = replicationServers;
    this.rcvWindow = window;
    this.maxRcvWindow = window;
    this.halfRcvWindow = window / 2;
    this.heartbeatInterval = heartbeatInterval;
    return needToRestartSession;
  }
  /**
@@ -1900,14 +1755,13 @@
  {
    try
    {
      ChangeStatusMsg csMsg = new ChangeStatusMsg(ServerStatus.INVALID_STATUS,
        newStatus);
      session.publish(csMsg);
    } catch (IOException ex)
    {
      Message message = ERR_EXCEPTION_SENDING_CS.get(
        baseDn.toNormalizedString(),
        baseDn,
        Short.toString(serverId),
        ex.getLocalizedMessage() + stackTraceToSingleLineString(ex));
      logError(message);
@@ -1922,4 +1776,48 @@
  {
    this.groupId = groupId;
  }
  /**
   * Gets the info for DSs in the topology (except us).
   * @return The info for DSs in the topology (except us)
   */
  public List<DSInfo> getDsList()
  {
    return dsList;
  }
  /**
   * Gets the info for RSs in the topology (except the one we are connected
   * to).
   * @return The info for RSs in the topology (except the one we are connected
   * to)
   */
  public List<RSInfo> getRsList()
  {
    return rsList;
  }
  /**
   * Processes an incoming TopologyMsg.
   * Updates the structures for the local view of the topology.
   *
   * @param topoMsg The topology information received from RS.
   */
  public void receiveTopo(TopologyMsg topoMsg)
  {
    if (debugEnabled())
      TRACER.debugInfo("Replication domain " + baseDn
        + " received topology info update:\n" + topoMsg);
    // Store new lists
    synchronized(getDsList())
    {
      synchronized(getRsList())
      {
        dsList = topoMsg.getDsList();
        rsList = topoMsg.getRsList();
      }
    }
  }
}
opends/src/server/org/opends/server/replication/service/ReplicationDomain.java
New file
@@ -0,0 +1,2433 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.service;
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.replication.common.StatusMachine.*;
import org.opends.server.replication.common.ChangeNumberGenerator;
import java.io.BufferedOutputStream;
import org.opends.server.types.Attribute;
import org.opends.server.core.DirectoryServer;
import java.util.Set;
import org.opends.server.replication.common.DSInfo;
import org.opends.server.replication.common.RSInfo;
import java.util.HashMap;
import java.util.Map;
import org.opends.server.config.ConfigException;
import java.util.Collection;
import org.opends.server.replication.protocol.ReplSessionSecurity;
import org.opends.server.replication.protocol.ResetGenerationIdMsg;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.api.DirectoryThread;
import org.opends.server.backends.task.Task;
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.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.AckMsg;
import org.opends.server.replication.protocol.ChangeStatusMsg;
import org.opends.server.replication.protocol.DoneMsg;
import org.opends.server.replication.protocol.EntryMsg;
import org.opends.server.replication.protocol.ErrorMsg;
import org.opends.server.replication.protocol.HeartbeatMsg;
import org.opends.server.replication.protocol.InitializeRequestMsg;
import org.opends.server.replication.protocol.InitializeTargetMsg;
import org.opends.server.replication.protocol.ProtocolSession;
import org.opends.server.replication.protocol.ProtocolVersion;
import org.opends.server.replication.protocol.ReplicationMsg;
import org.opends.server.replication.protocol.RoutableMsg;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.ResultCode;
/**
 * This class should be used as a base for Replication implementations.
 * <p>
 * It is intended that developer in need of a replication mechanism
 * subclass this class with their own implementation.
 * <p>
 *   The startup phase of the ReplicationDomain subclass,
 *   should read the list of replication servers from the configuration,
 *   instantiate a {@link ServerState} then start the publish service
 *   by calling
 *   {@link #startPublishService(Collection, ServerState, int, long)}.
 *   At this point it can start calling the {@link #publish(UpdateMsg)}
 *   method if needed.
 * <p>
 *   When the startup phase reach the point when the subclass is ready
 *   to handle updates the Replication Domain implementation should call the
 *   {@link #startListenService()} method.
 *   At this point a Listener thread is created on the Replication Service
 *   and which can start receiving updates.
 * <p>
 *   When updates are received the Replication Service calls the
 *   {@link #processUpdate(UpdateMsg)} method.
 *   ReplicationDomain implementation should implement the appropriate code
 *   for replaying the update on the local repository.
 *   When fully done the subclass must call the
 *   {@link #processUpdateDone(UpdateMsg)} method.
 *   This allows to process the update asynchronously if necessary.
 *
 * <p>
 *   To propagate changes to other replica, a ReplicationDomain implementation
 *   must use the {@link #publish(UpdateMsg)} method.
 * <p>
 *   If the Full Initialization process is needed then implementation
 *   for {@link #importBackend(InputStream)} and
 *   {@link #exportBackend(OutputStream)} must be
 *   provided.
 * <p>
 *   Full Initialization of a replica can be triggered by LDAP clients
 *   by creating InitializeTasks or InitializeTargetTask.
 *   Full initialization can also by triggered from the ReplicationDomain
 *   implementation using methods {@link #initializeRemote(short, Task)}
 *   or {@link #initializeFromRemote(short, Task)}.
 * <p>
 *   At shutdown time, the {@link #stopDomain()} method should be called to
 *   cleanly stop the replication service.
 */
public abstract class ReplicationDomain
{
  /**
   * Current status for this replicated domain.
   */
  private ServerStatus status = ServerStatus.NOT_CONNECTED_STATUS;
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   *  An identifier for the Replication Service.
   *  All Replication Domain using this identifier will be connected
   *  through the Replication Service.
   */
  private final String serviceID;
  /**
   * The identifier of this Replication Domain inside the
   * Replication Service.
   * Each Domain must use a unique ServerID.
   */
  private final short serverID;
  /**
   * The ReplicationBroker that is used by this ReplicationDomain to
   * connect to the ReplicationService.
   */
  private ReplicationBroker broker;
  /**
   * This Map is used to store all outgoing assured messages in order
   * to be able to correlate all the coming back acks to the original
   * operation.
   */
  private final SortedMap<ChangeNumber, UpdateMsg> waitingAckMsgs =
    new TreeMap<ChangeNumber, UpdateMsg>();
  /**
   * The context related to an import or export being processed
   * Null when none is being processed.
   */
  private IEContext ieContext = null;
  /**
   * The Thread waiting for incoming update messages for this domain and pushing
   * them to the global incoming update message queue for later processing by
   * replay threads.
   */
  private ListenerThread listenerThread;
  /**
   * A Map used to store all the ReplicationDomains created on this server.
   */
  private static Map<String, ReplicationDomain> domains =
    new HashMap<String, ReplicationDomain>();
  /**
   * The Monitor in charge of replication monitoring.
   */
  private ReplicationMonitor monitor;
  /*
   * Assured mode properties
   */
  // Is assured mode enabled or not for this domain ?
  private boolean assured = false;
  // Assured sub mode (used when assured is true)
  private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE;
  // Safe Data level (used when assuredMode is SAFE_DATA)
  private byte assuredSdLevel = (byte)1;
  // The timeout in ms that should be used, when waiting for assured acks
  private long assuredTimeout = 1000;
  // Group id
  private byte groupId = (byte)1;
  // Referrals urls to be published to other servers of the topology
  // TODO: fill that with all currently opened urls if no urls configured
  private List<String> refUrls = new ArrayList<String>();
  /**
   * A set of counters used for Monitoring.
   */
  private AtomicInteger numProcessedUpdates = new AtomicInteger();
  private AtomicInteger numRcvdUpdates = new AtomicInteger(0);
  private AtomicInteger numSentUpdates = new AtomicInteger(0);
  /* Assured replication monitoring counters */
  // Number of updates sent in Assured Mode, Safe Read
  private AtomicInteger assuredSrSentUpdates = new AtomicInteger(0);
  // Number of updates sent in Assured Mode, Safe Read, that have been
  // successfully acknowledged
  private AtomicInteger assuredSrAcknowledgedUpdates = new AtomicInteger(0);
  // Number of updates sent in Assured Mode, Safe Read, that have not been
  // successfully acknowledged (either because of timeout, wrong status or error
  // at replay)
  private AtomicInteger assuredSrNotAcknowledgedUpdates =
    new AtomicInteger(0);
  // Number of updates sent in Assured Mode, Safe Read, that have not been
  // successfully acknowledged because of timeout
  private AtomicInteger assuredSrTimeoutUpdates = new AtomicInteger(0);
  // Number of updates sent in Assured Mode, Safe Read, that have not been
  // successfully acknowledged because of wrong status
  private AtomicInteger assuredSrWrongStatusUpdates = new AtomicInteger(0);
  // Number of updates sent in Assured Mode, Safe Read, that have not been
  // successfully acknowledged because of replay error
  private AtomicInteger assuredSrReplayErrorUpdates = new AtomicInteger(0);
  // Multiple values allowed: number of updates sent in Assured Mode, Safe Read,
  // that have not been successfully acknowledged (either because of timeout,
  // wrong status or error at replay) for a particular server (DS or RS). String
  // format: <server id>:<number of failed updates>
  private Map<Short,Integer> assuredSrServerNotAcknowledgedUpdates =
    new HashMap<Short,Integer>();
  // Number of updates sent in Assured Mode, Safe Data
  private AtomicInteger assuredSdSentUpdates = new AtomicInteger(0);
  // Number of updates sent in Assured Mode, Safe Data, that have been
  // successfully acknowledged
  private AtomicInteger assuredSdAcknowledgedUpdates = new AtomicInteger(0);
  // Number of updates sent in Assured Mode, Safe Data, that have not been
  // successfully acknowledged because of timeout
  private AtomicInteger assuredSdTimeoutUpdates = new AtomicInteger(0);
  // Multiple values allowed: number of updates sent in Assured Mode, Safe Data,
  // that have not been successfully acknowledged because of timeout for a
  // particular RS. String format: <server id>:<number of failed updates>
  private Map<Short,Integer> assuredSdServerTimeoutUpdates =
    new HashMap<Short,Integer>();
  /* Status related monitoring fields */
  // Indicates the date when the status changed. This may be used to indicate
  // the date the session with the current replication server started (when
  // status is NORMAL for instance). All the above assured monitoring fields
  // are also resetted each time the status is changed
  private Date lastStatusChangeDate = new Date();
  /**
   * The state maintained by the Concrete Class.
   */
  private final ServerState state;
  /**
   * The generator that will be used to generate {@link ChangeNumber}
   * for this domain.
   */
  private final ChangeNumberGenerator generator;
  /**
   * Returns the {@link ChangeNumberGenerator} that will be used to
   * generate {@link ChangeNumber} for this domain.
   *
   * @return The {@link ChangeNumberGenerator} that will be used to
   *         generate {@link ChangeNumber} for this domain.
   */
  public ChangeNumberGenerator getGenerator()
  {
    return generator;
  }
  /**
   * Creates a ReplicationDomain with the provided parameters.
   *
   * @param serviceID  The identifier of the Replication Domain to which
   *                   this object is participating.
   * @param serverID   The identifier of the server that is participating
   *                   to the Replication Domain.
   *                   This identifier should be different for each server that
   *                   is participating to a given Replication Domain.
   */
  public ReplicationDomain(String serviceID, short serverID)
  {
    this.serviceID = serviceID;
    this.serverID = serverID;
    this.state = new ServerState();
    this.generator = new ChangeNumberGenerator(serverID, state);
    domains.put(serviceID, this);
  }
  /**
   * Set the initial status of the domain and perform necessary initializations.
   * This method will be called by the Broker each time the ReplicationBroker
   * establish a new session to a Replication Server.
   *
   * Implementations may override this method when they need to perform
   * additional computing after session establishment.
   * The default implementation should be sufficient for ReplicationDomains
   * that don't need to perform additional computing.
   *
   * @param initStatus              The status to enter the state machine with.
   * @param replicationServerState  The ServerState of the ReplicationServer
   *                                with which the session was established.
   * @param session                 The ProtocolSession that is currently used.
   */
  public void sessionInitiated(
      ServerStatus initStatus,
      ServerState replicationServerState,
      ProtocolSession session)
  {
    // Sanity check: is it a valid initial status?
    if (!isValidInitialStatus(initStatus))
    {
      Message msg = ERR_DS_INVALID_INIT_STATUS.get(initStatus.toString(),
        serviceID, Short.toString(serverID));
      logError(msg);
    } else
    {
      status = initStatus;
    }
  }
  /**
   * Processes an incoming ChangeStatusMsg. Compute new status according to
   * given order. Then update domain for being compliant with new status
   * definition.
   * @param csMsg The received status message
   */
  private void receiveChangeStatus(ChangeStatusMsg csMsg)
  {
    if (debugEnabled())
      TRACER.debugInfo("Replication domain " + serviceID +
        " received change status message:\n" + csMsg);
    ServerStatus reqStatus = csMsg.getRequestedStatus();
    // Translate requested status to a state machine event
    StatusMachineEvent event = StatusMachineEvent.statusToEvent(reqStatus);
    if (event == StatusMachineEvent.INVALID_EVENT)
    {
      Message msg = ERR_DS_INVALID_REQUESTED_STATUS.get(reqStatus.toString(),
        serviceID, Short.toString(serverID));
      logError(msg);
      return;
    }
    // Set the new status to the requested one
    setNewStatus(event);
  }
  /**
   * Called when first connection or disconnection detected.
   */
  void toNotConnectedStatus()
  {
    // Go into not connected status
    setNewStatus(StatusMachineEvent.TO_NOT_CONNECTED_STATUS_EVENT);
  }
  /**
   * Perform whatever actions are needed to apply properties for being
   * compliant with new status. Must be called in synchronized section for
   * status. The new status is already set in status variable.
   */
  private void updateDomainForNewStatus()
  {
    switch (status)
    {
      case NOT_CONNECTED_STATUS:
        break;
      case NORMAL_STATUS:
        break;
      case DEGRADED_STATUS:
        break;
      case FULL_UPDATE_STATUS:
        // Signal RS we just entered the full update status
        broker.signalStatusChange(status);
        break;
      case BAD_GEN_ID_STATUS:
        break;
      default:
        if (debugEnabled())
          TRACER.debugInfo("updateDomainForNewStatus: unexpected status: " +
            status);
    }
  }
  /**
   * Gets the status for this domain.
   * @return The status for this domain.
   */
  public ServerStatus getStatus()
  {
    return status;
  }
  /**
   * Gets the identifier of this domain.
   *
   * @return The identifier for this domain.
   */
  public String getServiceID()
  {
    return serviceID;
  }
  /**
   * Get the server ID.
   * @return The server ID.
   */
  public short getServerId()
  {
    return serverID;
  }
  /**
   * Tells if assured replication is enabled for this domain.
   * @return True if assured replication is enabled for this domain.
   */
  public boolean isAssured()
  {
    return assured;
  }
  /**
   * Gives the mode for the assured replication of the domain.
   * @return The mode for the assured replication of the domain.
   */
  public AssuredMode getAssuredMode()
  {
    return assuredMode;
  }
  /**
   * Gives the assured level of the replication of the domain.
   * @return The assured level of the replication of the domain.
   */
  public byte getAssuredSdLevel()
  {
    return assuredSdLevel;
  }
  /**
   * Gives the assured timeout of the replication of the domain (in ms).
   * @return The assured timeout of the replication of the domain.
   */
  public long getAssuredTimeout()
  {
    return assuredTimeout;
  }
  /**
   * Gets the group id for this domain.
   * @return The group id for this domain.
   */
  public byte getGroupId()
  {
    return groupId;
  }
  /**
   * Gets the referrals URLs this domain publishes.
   * @return The referrals URLs this domain publishes.
   */
  public List<String> getRefUrls()
  {
    return refUrls;
  }
  /**
   * Gets the info for DSs in the topology (except us).
   * @return The info for DSs in the topology (except us)
   */
  public List<DSInfo> getDsList()
  {
    return broker.getDsList();
  }
  /**
   * Gets the info for RSs in the topology (except the one we are connected
   * to).
   * @return The info for RSs in the topology (except the one we are connected
   * to)
   */
  public List<RSInfo> getRsList()
  {
    return broker.getRsList();
  }
  /**
   * Gets the server ID of the Replication Server to which the domain
   * is currently connected.
   *
   * @return The server ID of the Replication Server to which the domain
   *         is currently connected.
   */
  public short getRsServerId()
  {
    return broker.getRsServerId();
  }
  /**
   * Increment the number of processed updates.
   */
  private void incProcessedUpdates()
  {
    numProcessedUpdates.incrementAndGet();
  }
  /**
   * get the number of updates replayed by the replication.
   *
   * @return The number of updates replayed by the replication
   */
  int getNumProcessedUpdates()
  {
    if (numProcessedUpdates != null)
      return numProcessedUpdates.get();
    else
      return 0;
  }
  /**
   * get the number of updates received by the replication plugin.
   *
   * @return the number of updates received
   */
  int getNumRcvdUpdates()
  {
    if (numRcvdUpdates != null)
      return numRcvdUpdates.get();
    else
      return 0;
  }
  /**
   * Get the number of updates sent by the replication plugin.
   *
   * @return the number of updates sent
   */
  int getNumSentUpdates()
  {
    if (numSentUpdates != null)
      return numSentUpdates.get();
    else
      return 0;
  }
  /**
   * Set the list of Referrals that should be returned when an
   * operation needs to be redirected to this server.
   *
   * @param referralsUrl The list of referrals.
   */
  public void setURLs(Set<String> referralsUrl)
  {
    for (String url : referralsUrl)
      this.refUrls.add(url);
  }
  /**
   * Set the timeout of the assured replication.
   *
   * @param assuredTimeout the timeout of the assured replication.
   */
  public void setAssuredTimeout(long assuredTimeout)
  {
    this.assuredTimeout = assuredTimeout;
  }
  /**
   * Sets the groupID.
   *
   * @param groupId The groupID.
   */
  public void setGroupId(byte groupId)
  {
    this.groupId = groupId;
  }
  /**
   * Sets the level of assured replication.
   *
   * @param assuredSdLevel The level of assured replication.
   */
  public void setAssuredSdLevel(byte assuredSdLevel)
  {
    this.assuredSdLevel = assuredSdLevel;
  }
  /**
   * Sets the assured replication mode.
   *
   * @param dataMode The assured replication mode.
   */
  public void setAssuredMode(AssuredMode dataMode)
  {
    this.assuredMode = dataMode;
  }
  /**
   * Sets assured replication.
   *
   * @param assured A boolean indicating if assured replication should be used.
   */
  public void setAssured(boolean assured)
  {
    this.assured = assured;
  }
  /**
   * Receives an update message from the replicationServer.
   * also responsible for updating the list of pending changes
   * @return the received message - null if none
   */
  UpdateMsg receive()
  {
    UpdateMsg update = null;
    while (update == null)
    {
      InitializeRequestMsg initMsg = null;
      ReplicationMsg msg;
      try
      {
        msg = broker.receive();
        if (msg == null)
        {
          // The server is in the shutdown process
          return null;
        }
        if (debugEnabled())
          if (!(msg instanceof HeartbeatMsg))
            TRACER.debugVerbose("Message received <" + msg + ">");
        if (msg instanceof AckMsg)
        {
          AckMsg ack = (AckMsg) msg;
          receiveAck(ack);
        }
        else if (msg instanceof InitializeRequestMsg)
        {
          // Another server requests us to provide entries
          // for a total update
          initMsg = (InitializeRequestMsg)msg;
        }
        else if (msg instanceof InitializeTargetMsg)
        {
          // Another server is exporting its entries to us
          InitializeTargetMsg importMsg = (InitializeTargetMsg) msg;
          try
          {
            // This must be done while we are still holding the
            // broker lock because we are now going to receive a
            // bunch of entries from the remote server and we
            // want the import thread to catch them and
            // not the ListenerThread.
            initialize(importMsg);
          }
          catch(DirectoryException de)
          {
            // Returns an error message to notify the sender
            ErrorMsg errorMsg =
              new ErrorMsg(importMsg.getsenderID(),
                  de.getMessageObject());
            MessageBuilder mb = new MessageBuilder();
            mb.append(de.getMessageObject());
            TRACER.debugInfo(Message.toString(mb.toMessage()));
            broker.publish(errorMsg);
          }
        }
        else if (msg instanceof ErrorMsg)
        {
          if (ieContext != null)
          {
            // This is an error termination for the 2 following cases :
            // - either during an export
            // - or before an import really started
            //   For example, when we publish a request and the
            //  replicationServer did not find any import source.
            abandonImportExport((ErrorMsg)msg);
          }
          else
          {
            /*
             * Log error message
             */
            ErrorMsg errorMsg = (ErrorMsg)msg;
            logError(ERR_ERROR_MSG_RECEIVED.get(
                errorMsg.getDetails()));
          }
        }
        else if (msg instanceof ChangeStatusMsg)
        {
          ChangeStatusMsg csMsg = (ChangeStatusMsg)msg;
          receiveChangeStatus(csMsg);
        }
        else if (msg instanceof UpdateMsg)
        {
          generator.adjust(((UpdateMsg) msg).getChangeNumber());
          update = (UpdateMsg) msg;
          generator.adjust(update.getChangeNumber());
        }
      }
      catch (SocketTimeoutException e)
      {
        // just retry
      }
      // Test if we have received and export request message and
      // if that's the case handle it now.
      // This must be done outside of the portion of code protected
      // by the broker lock so that we keep receiveing update
      // when we are doing and export and so that a possible
      // closure of the socket happening when we are publishing the
      // entries to the remote can be handled by the other
      // replay thread when they call this method and therefore the
      // broker.receive() method.
      if (initMsg != null)
      {
        // Do this work in a thread to allow replay thread continue working
        ExportThread exportThread = new ExportThread(initMsg.getsenderID());
        exportThread.start();
      }
    }
    numRcvdUpdates.incrementAndGet();
    return update;
  }
  /**
   * Wait for the processing of an assured message.
   *
   * @param msg The UpdateMsg for which we are waiting for an ack.
   * @throws TimeoutException When the configured timeout occurs waiting for the
   * ack.
   */
  private void waitForAck(UpdateMsg msg) throws TimeoutException
  {
    // Wait for the ack to be received, timing out if necessary
    long startTime = System.currentTimeMillis();
    synchronized (msg)
    {
      ChangeNumber cn = msg.getChangeNumber();
      while (waitingAckMsgs.containsKey(cn))
      {
        try
        {
          // WARNING: this timeout may be difficult to optimize: too low, it
          // may use too much CPU, too high, it may penalize performance...
          msg.wait(10);
        } catch (InterruptedException e)
        {
          if (debugEnabled())
          {
            TRACER.debugInfo("waitForAck method interrupted for replication " +
              "serviceID: " + serviceID);
          }
          break;
        }
        // Timeout ?
        if ( (System.currentTimeMillis() - startTime) >= assuredTimeout )
        {
          // Timeout occured, be sure that ack is not being received and if so,
          // remove the update from the wait list, log the timeout error and
          // also update assured monitoring counters
          UpdateMsg update;
          synchronized (waitingAckMsgs)
          {
            update = waitingAckMsgs.remove(cn);
          }
          if (update != null)
          {
            // No luck, this is a real timeout
            // Increment assured replication monitoring counters
            switch (msg.getAssuredMode())
            {
              case SAFE_READ_MODE:
                assuredSrNotAcknowledgedUpdates.incrementAndGet();
                assuredSrTimeoutUpdates.incrementAndGet();
                // Increment number of errors for our RS
                updateAssuredErrorsByServer(
                  assuredSrServerNotAcknowledgedUpdates,
                  broker.getRsServerId());
                break;
              case SAFE_DATA_MODE:
                assuredSdTimeoutUpdates.incrementAndGet();
                // Increment number of errors for our RS
                updateAssuredErrorsByServer(assuredSdServerTimeoutUpdates,
                  broker.getRsServerId());
                break;
              default:
              // Should no happen
            }
            throw new TimeoutException("No ack received for message cn: " + cn +
              " and replication servceID: " + serviceID + " after " +
              assuredTimeout + " ms.");
          } else
          {
            // Ack received just before timeout limit: we can exit
            break;
          }
        }
      }
    }
  }
  /**
   * Updates the passed monitoring list of errors received for assured messages
   * (safe data or safe read, depending of the passed list to update) for a
   * particular server in the list. This increments the counter of error for the
   * passed server, or creates an initial value of 1 error for it if the server
   * is not yet present in the map.
   * @param errorList
   * @param serverId
   */
  private void updateAssuredErrorsByServer(Map<Short,Integer> errorsByServer,
    Short serverId)
  {
    synchronized (errorsByServer)
    {
      Integer serverErrCount = errorsByServer.get(serverId);
      if (serverErrCount == null)
      {
        // Server not present in list, create an entry with an
        // initial number of errors set to 1
        errorsByServer.put(serverId, 1);
      } else
      {
        // Server already present in list, just increment number of
        // errors for the server
        int val = serverErrCount.intValue();
        val++;
        errorsByServer.put(serverId, val);
      }
    }
  }
  /**
   * Do the necessary processing when an AckMsg is received.
   *
   * @param ack The AckMsg that was received.
   */
  private void receiveAck(AckMsg ack)
  {
    UpdateMsg update;
    ChangeNumber changeNumber = ack.getChangeNumber();
    // Remove the message for pending ack list (this may already make the thread
    // that is waiting for the ack be aware of its reception)
    synchronized (waitingAckMsgs)
    {
      update = waitingAckMsgs.remove(changeNumber);
    }
    // Signal waiting thread ack has been received
    if (update != null)
    {
      synchronized (update)
      {
        update.notify();
      }
      // Analyze status of embedded in the ack to see if everything went well
      boolean hasTimeout = ack.hasTimeout();
      boolean hasReplayErrors = ack.hasReplayError();
      boolean hasWrongStatus = ack.hasWrongStatus();
      AssuredMode updateAssuredMode = update.getAssuredMode();
      if ( hasTimeout || hasReplayErrors || hasWrongStatus)
      {
        // Some problems detected: message not correclty reached every requested
        // servers. Log problem
        Message errorMsg = ERR_DS_RECEIVED_ACK_ERROR.get(serviceID,
          update.toString(), ack.errorsToString());
        logError(errorMsg);
        List<Short> failedServers = ack.getFailedServers();
        // Increment assured replication monitoring counters
        switch (updateAssuredMode)
        {
          case SAFE_READ_MODE:
            assuredSrNotAcknowledgedUpdates.incrementAndGet();
            if (hasTimeout)
              assuredSrTimeoutUpdates.incrementAndGet();
            if (hasReplayErrors)
              assuredSrReplayErrorUpdates.incrementAndGet();
            if (hasWrongStatus)
              assuredSrWrongStatusUpdates.incrementAndGet();
            if (failedServers != null) // This should always be the case !
            {
              for(Short sid : failedServers)
              {
                updateAssuredErrorsByServer(
                  assuredSrServerNotAcknowledgedUpdates, sid);
              }
            }
            break;
          case SAFE_DATA_MODE:
            // The only possible cause of ack error in safe data mode is timeout
            if (hasTimeout) // So should always be the case
              assuredSdTimeoutUpdates.incrementAndGet();
            if (failedServers != null) // This should always be the case !
            {
              for(Short sid : failedServers)
              {
                updateAssuredErrorsByServer(
                  assuredSdServerTimeoutUpdates, sid);
              }
            }
            break;
          default:
          // Should no happen
        }
      } else
      {
        // Update has been acknowledged without errors
        // Increment assured replication monitoring counters
        switch (updateAssuredMode)
        {
          case SAFE_READ_MODE:
            assuredSrAcknowledgedUpdates.incrementAndGet();
            break;
          case SAFE_DATA_MODE:
            assuredSdAcknowledgedUpdates.incrementAndGet();
            break;
          default:
          // Should no happen
        }
      }
    }
  }
  /**
   * Retrieves a replication domain based on the baseDn.
   *
   * @param serviceID           The identifier of the domain to retrieve.
   *
   * @return                    The domain retrieved.
   *
   * @throws DirectoryException When an error occurred or no domain
   *                            match the provided baseDn.
   */
  static ReplicationDomain retrievesReplicationDomain(String serviceID)
  throws DirectoryException
  {
    ReplicationDomain replicationDomain = domains.get(serviceID);
    if (replicationDomain == null)
    {
      MessageBuilder mb = new MessageBuilder(ERR_NO_MATCHING_DOMAIN.get());
      mb.append(" ");
      mb.append(serviceID);
      throw new DirectoryException(ResultCode.OTHER,
         mb.toMessage());
    }
    return replicationDomain;
  }
  /*
   * After this point the code is related to the Total Update.
   */
  /**
   * This thread is launched when we want to export data to another server that
   * has requested to be initialized with the data of our backend.
   */
  private class ExportThread extends DirectoryThread
  {
    // Id of server that will receive updates
    private short target;
    /**
     * Constructor for the ExportThread.
     *
     * @param target Id of server that will receive updates
     */
    public ExportThread(short target)
    {
      super("Export thread " + serverID);
      this.target = target;
    }
    /**
     * Run method for this class.
     */
    public void run()
    {
      if (debugEnabled())
      {
        TRACER.debugInfo("Export thread starting.");
      }
      try
      {
        initializeRemote(target, target, null);
      } catch (DirectoryException de)
      {
      // An error message has been sent to the peer
      // Nothing more to do locally
      }
      if (debugEnabled())
      {
        TRACER.debugInfo("Export thread stopping.");
      }
    }
  }
  /**
   * This class contain the context related to an import or export
   * launched on the domain.
   */
  private class IEContext
  {
    // The task that initiated the operation.
    Task initializeTask;
    // The input stream for the import
    ReplInputStream ldifImportInputStream = null;
    // The target in the case of an export
    short exportTarget = RoutableMsg.UNKNOWN_SERVER;
    // The source in the case of an import
    short importSource = RoutableMsg.UNKNOWN_SERVER;
    // The total entry count expected to be processed
    long entryCount = 0;
    // The count for the entry not yet processed
    long entryLeftCount = 0;
    // The exception raised when any
    DirectoryException exception = null;
    // A boolean indicating if the context is related to an
    // import or an export.
    boolean importInProgress;
    /**
     * Creates a new IEContext.
     *
     * @param importInProgress true if the IEContext will be used
     *                         for and import, false if the IEContext
     *                         will be used for and export.
     */
    public IEContext(boolean importInProgress)
    {
      this.importInProgress = importInProgress;
    }
    /**
     * Initializes the import/export counters with the provider value.
     * @param total
     * @param left
     * @throws DirectoryException
     */
    public void setCounters(long total, long left)
      throws DirectoryException
    {
      entryCount = total;
      entryLeftCount = left;
      if (initializeTask != null)
      {
        if (initializeTask instanceof InitializeTask)
        {
          ((InitializeTask)initializeTask).setTotal(entryCount);
          ((InitializeTask)initializeTask).setLeft(entryCount);
        }
        else if (initializeTask instanceof InitializeTargetTask)
        {
          ((InitializeTargetTask)initializeTask).setTotal(entryCount);
          ((InitializeTargetTask)initializeTask).setLeft(entryCount);
        }
      }
    }
    /**
     * Update the counters of the task for each entry processed during
     * an import or export.
     * @throws DirectoryException
     */
    public void updateCounters()
      throws DirectoryException
    {
      entryLeftCount--;
      if (initializeTask != null)
      {
        if (initializeTask instanceof InitializeTask)
        {
          ((InitializeTask)initializeTask).setLeft(entryLeftCount);
        }
        else if (initializeTask instanceof InitializeTargetTask)
        {
          ((InitializeTargetTask)initializeTask).setLeft(entryLeftCount);
        }
      }
    }
    /**
     * Update the counters of the task for each entry processed during
     * an import or export.
     *
     * @param  entriesDone The number of entries that were processed
     *                     since the last time this method was called.
     *
     * @throws DirectoryException
     */
    public void updateCounters(int entriesDone)
      throws DirectoryException
    {
      entryLeftCount -= entriesDone;
      if (initializeTask != null)
      {
        if (initializeTask instanceof InitializeTask)
        {
          ((InitializeTask)initializeTask).setLeft(entryLeftCount);
        }
        else if (initializeTask instanceof InitializeTargetTask)
        {
          ((InitializeTargetTask)initializeTask).setLeft(entryLeftCount);
        }
      }
    }
    /**
     * {@inheritDoc}
     */
    public String toString()
    {
      return new String("[ Entry count=" + this.entryCount +
                        ", Entry left count=" + this.entryLeftCount + "]");
    }
  }
  /**
   * Verifies that the given string represents a valid source
   * from which this server can be initialized.
   *
   * @param targetString The string representing the source
   * @return The source as a short value
   * @throws DirectoryException if the string is not valid
   */
  short decodeTarget(String targetString)
  throws DirectoryException
  {
    short  target = 0;
    Throwable cause;
    if (targetString.equalsIgnoreCase("all"))
    {
      return RoutableMsg.ALL_SERVERS;
    }
    // So should be a serverID
    try
    {
      target = Integer.decode(targetString).shortValue();
      if (target >= 0)
      {
        // FIXME Could we check now that it is a know server in the domain ?
      }
      return target;
    }
    catch(Exception e)
    {
      cause = e;
    }
    ResultCode resultCode = ResultCode.OTHER;
    Message message = ERR_INVALID_EXPORT_TARGET.get();
    if (cause != null)
      throw new DirectoryException(
          resultCode, message, cause);
    else
      throw new DirectoryException(
          resultCode, message);
  }
  /**
   * Process the initialization of some other server or servers in the topology
   * specified by the target argument.
   * @param target The target that should be initialized
   * @param initTask The task that triggers this initialization and that should
   *                 be updated with its progress.
   *
   * @exception DirectoryException When an error occurs.
   */
  void initializeRemote(short target, Task initTask)
  throws DirectoryException
  {
    initializeRemote(target, serverID, initTask);
  }
  /**
   * Process the initialization of some other server or servers in the topology
   * specified by the target argument when this initialization specifying the
   * server that requests the initialization.
   *
   * @param target The target that should be initialized.
   * @param requestorID The server that initiated the export.
   * @param initTask The task that triggers this initialization and that should
   *  be updated with its progress.
   *
   * @exception DirectoryException When an error occurs.
   */
  void initializeRemote(short target, short requestorID, Task initTask)
  throws DirectoryException
  {
    Message msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_START.get(
      Short.toString(serverID),
      serviceID,
      Short.toString(requestorID));
    logError(msg);
    boolean contextAcquired=false;
    acquireIEContext(false);
    contextAcquired = true;
    ieContext.exportTarget = target;
    if (initTask != null)
    {
      ieContext.initializeTask = initTask;
    }
    // The number of entries to be exported is the number of entries under
    // the base DN entry and the base entry itself.
    long entryCount = this.countEntries();
    ieContext.setCounters(entryCount, entryCount);
    // Send start message to the peer
    InitializeTargetMsg initializeMessage = new InitializeTargetMsg(
        serviceID, serverID, target, requestorID, entryCount);
    broker.publish(initializeMessage);
    try
    {
      exportBackend(new BufferedOutputStream(new ReplOutputStream(this)));
      // Notify the peer of the success
      DoneMsg doneMsg = new DoneMsg(serverID,
          initializeMessage.getDestination());
      broker.publish(doneMsg);
      releaseIEContext();
    }
    catch(DirectoryException de)
    {
      // Notify the peer of the failure
      ErrorMsg errorMsg =
        new ErrorMsg(target,
                         de.getMessageObject());
      broker.publish(errorMsg);
      if (contextAcquired)
        releaseIEContext();
      throw(de);
    }
    msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_END.get(
      Short.toString(serverID),
      serviceID,
      Short.toString(requestorID));
    logError(msg);
  }
  /**
   * Get the ServerState maintained by the Concrete class.
   *
   * @return the ServerState maintained by the Concrete class.
   */
  public ServerState getServerState()
  {
    return state;
  }
  private synchronized void acquireIEContext(boolean importInProgress)
  throws DirectoryException
  {
    if (ieContext != null)
    {
      // Rejects 2 simultaneous exports
      Message message = ERR_SIMULTANEOUS_IMPORT_EXPORT_REJECTED.get();
      throw new DirectoryException(ResultCode.OTHER,
          message);
    }
    ieContext = new IEContext(importInProgress);
  }
  private synchronized void releaseIEContext()
  {
    ieContext = null;
  }
  /**
   * Processes an error message received while an import/export is
   * on going.
   * @param errorMsg The error message received.
   */
  void abandonImportExport(ErrorMsg errorMsg)
  {
    // FIXME TBD Treat the case where the error happens while entries
    // are being exported
    if (debugEnabled())
      TRACER.debugVerbose(
          " abandonImportExport:" + this.serverID +
          " serviceID: " + this.serviceID +
          " Error Msg received: " + errorMsg);
    if (ieContext != null)
    {
      ieContext.exception = new DirectoryException(ResultCode.OTHER,
          errorMsg.getDetails());
      if (ieContext.initializeTask instanceof InitializeTask)
      {
        // Update the task that initiated the import
        ((InitializeTask)ieContext.initializeTask).
        updateTaskCompletionState(ieContext.exception);
        releaseIEContext();
      }
    }
  }
  /**
   * Receives bytes related to an entry in the context of an import to
   * initialize the domain (called by ReplLDIFInputStream).
   *
   * @return The bytes. Null when the Done or Err message has been received
   */
  byte[] receiveEntryBytes()
  {
    ReplicationMsg msg;
    while (true)
    {
      try
      {
        msg = broker.receive();
        if (debugEnabled())
          TRACER.debugVerbose(
              " sid:" + serverID +
              " base DN:" + serviceID +
              " Import EntryBytes received " + msg);
        if (msg == null)
        {
          // The server is in the shutdown process
          return null;
        }
        if (msg instanceof EntryMsg)
        {
          EntryMsg entryMsg = (EntryMsg)msg;
          byte[] entryBytes = entryMsg.getEntryBytes();
          ieContext.updateCounters(countEntryLimits(entryBytes));
          return entryBytes;
        }
        else if (msg instanceof DoneMsg)
        {
          // This is the normal termination of the import
          // No error is stored and the import is ended
          // by returning null
          return null;
        }
        else if (msg instanceof ErrorMsg)
        {
          // This is an error termination during the import
          // The error is stored and the import is ended
          // by returning null
          ErrorMsg errorMsg = (ErrorMsg)msg;
          ieContext.exception = new DirectoryException(
                                      ResultCode.OTHER,
                                      errorMsg.getDetails());
          return null;
        }
        else
        {
          // Other messages received during an import are trashed
        }
      }
      catch(Exception e)
      {
        // TODO: i18n
        ieContext.exception = new DirectoryException(ResultCode.OTHER,
            Message.raw("received an unexpected message type" +
                e.getLocalizedMessage()));
      }
    }
  }
  /**
   * Count the number of entries in the provided byte[].
   * This is based on the hypothesis that the entries are separated
   * by a "\n\n" String.
   *
   * @param   entryBytes
   * @return  The number of entries in the provided byte[].
   */
  private int countEntryLimits(byte[] entryBytes)
  {
    return countEntryLimits(entryBytes, 0, entryBytes.length);
  }
  /**
   * Count the number of entries in the provided byte[].
   * This is based on the hypothesis that the entries are separated
   * by a "\n\n" String.
   *
   * @param   entryBytes
   * @return  The number of entries in the provided byte[].
   */
  private int countEntryLimits(byte[] entryBytes, int pos, int length)
  {
    int entryCount = 0;
    int count = 0;
    while (count<=length-2)
    {
      if ((entryBytes[pos+count] == '\n') && (entryBytes[pos+count+1] == '\n'))
      {
        entryCount++;
        count++;
      }
      count++;
    }
    return entryCount;
  }
  /**
   * Exports an entry in LDIF format.
   *
   * @param lDIFEntry The entry to be exported in byte[] form.
   * @param pos       The starting Position in the array.
   * @param length    Number of array elements to be copied.
   *
   * @throws IOException when an error occurred.
   */
  void exportLDIFEntry(byte[] lDIFEntry, int pos, int length) throws IOException
  {
    // If an error was raised - like receiving an ErrorMsg
    // we just let down the export.
    if (ieContext.exception != null)
    {
      IOException ioe = new IOException(ieContext.exception.getMessage());
      ieContext = null;
      throw ioe;
    }
    EntryMsg entryMessage = new EntryMsg(
        serverID, ieContext.exportTarget, lDIFEntry, pos, length);
    broker.publish(entryMessage);
    try
    {
      ieContext.updateCounters(countEntryLimits(lDIFEntry, pos, length));
    }
    catch (DirectoryException de)
    {
      throw new IOException(de.getMessage());
    }
  }
  /**
   * Initializes this domain from another source server.
   *
   * @param source The source from which to initialize
   * @param initTask The task that launched the initialization
   *                 and should be updated of its progress.
   * @throws DirectoryException when an error occurs
   */
  void initializeFromRemote(short source, Task initTask)
  throws DirectoryException
  {
    if (debugEnabled())
      TRACER.debugInfo("Entering initializeFromRemote");
    acquireIEContext(true);
    ieContext.initializeTask = initTask;
    InitializeRequestMsg initializeMsg = new InitializeRequestMsg(
        serviceID, serverID, source);
    // Publish Init request msg
    broker.publish(initializeMsg);
    // .. we expect to receive entries or err after that
  }
  /**
   * Initializes the domain's backend with received entries.
   * @param initializeMessage The message that initiated the import.
   * @exception DirectoryException Thrown when an error occurs.
   */
  void initialize(InitializeTargetMsg initializeMessage)
  throws DirectoryException
  {
    DirectoryException de = null;
    Message msg = NOTE_FULL_UPDATE_ENGAGED_FROM_REMOTE_START.get(
      Short.toString(serverID),
      serviceID,
      Long.toString(initializeMessage.getRequestorID()));
    logError(msg);
    // Go into full update status
    setNewStatus(StatusMachineEvent.TO_FULL_UPDATE_STATUS_EVENT);
    if (initializeMessage.getRequestorID() == serverID)
    {
      // The import responds to a request we did so the IEContext
      // is already acquired
    }
    else
    {
      acquireIEContext(true);
    }
    ieContext.importSource = initializeMessage.getsenderID();
    ieContext.entryLeftCount = initializeMessage.getEntryCount();
    ieContext.setCounters(
        initializeMessage.getEntryCount(),
        initializeMessage.getEntryCount());
    ieContext.ldifImportInputStream = new ReplInputStream(this);
    try
    {
      importBackend(new ReplInputStream(this));
      broker.reStart();
    }
    catch (DirectoryException e)
    {
      de = e;
    }
    finally
    {
      if ((ieContext != null)  && (ieContext.exception != null))
        de = ieContext.exception;
      // Update the task that initiated the import
      if ((ieContext != null ) && (ieContext.initializeTask != null))
      {
        ((InitializeTask)ieContext.initializeTask).
        updateTaskCompletionState(de);
      }
      releaseIEContext();
    }
    // Sends up the root error.
    if (de != null)
    {
      throw de;
    }
    msg = NOTE_FULL_UPDATE_ENGAGED_FROM_REMOTE_END.get(
      Short.toString(serverID),
      serviceID,
      Long.toString(initializeMessage.getRequestorID()));
    logError(msg);
  }
  /**
   * Sets the status to a new value depending of the passed status machine
   * event.
   * @param event The event that may make the status be changed
   */
  private void setNewStatus(StatusMachineEvent event)
  {
    ServerStatus newStatus =
      StatusMachine.computeNewStatus(status, event);
    if (newStatus == ServerStatus.INVALID_STATUS)
    {
      Message msg = ERR_DS_CANNOT_CHANGE_STATUS.get(serviceID,
        Short.toString(serverID), status.toString(), event.toString());
      logError(msg);
      return;
    }
    if (newStatus != status)
    {
      // Store new status
      status = newStatus;
      lastStatusChangeDate = new Date();
      // Reset assured monitoring counters (they are valid for a session with
      // the same status/RS)
      resetAssuredMonitoringCounters();
      if (debugEnabled())
        TRACER.debugInfo("Replication domain " + serviceID +
          " new status is: " + status);
      // Perform whatever actions are needed to apply properties for being
      // compliant with new status
      updateDomainForNewStatus();
    }
  }
  /**
   * Returns a boolean indicating if an import or export is currently
   * processed.
   *
   * @return The status
   */
  public boolean ieRunning()
  {
    return (ieContext != null);
  }
  /**
   * Verifies that the given string represents a valid source
   * from which this server can be initialized.
   * @param sourceString The string representing the source
   * @return The source as a short value
   * @throws DirectoryException if the string is not valid
   */
  short decodeSource(String sourceString)
  throws DirectoryException
  {
    short  source = 0;
    Throwable cause = null;
    try
    {
      source = Integer.decode(sourceString).shortValue();
      if ((source >= -1) && (source != serverID))
      {
        // TODO Verifies serverID is in the domain
        // We should check here that this is a server implied
        // in the current domain.
        return source;
      }
    }
    catch(Exception e)
    {
      cause = e;
    }
    ResultCode resultCode = ResultCode.OTHER;
    Message message = ERR_INVALID_IMPORT_SOURCE.get();
    if (cause != null)
    {
      throw new DirectoryException(
          resultCode, message, cause);
    }
    else
    {
      throw new DirectoryException(
          resultCode, message);
    }
  }
  /**
   * Reset the generationId of this domain in the whole topology.
   * A message is sent to the Replication Servers for them to reset
   * their change dbs.
   *
   * @param generationIdNewValue The new value of the generation Id.
   * @throws DirectoryException when an error occurs
   */
  void resetGenerationId(Long generationIdNewValue)
  throws DirectoryException
  {
    if (debugEnabled())
      TRACER.debugInfo(
          "Server id " + serverID + " and domain " + serviceID
          + "resetGenerationId" + generationIdNewValue);
    if (!isConnected())
    {
      ResultCode resultCode = ResultCode.OTHER;
      Message message = ERR_RESET_GENERATION_CONN_ERR_ID.get(
          serviceID);
      throw new DirectoryException(
         resultCode, message);
    }
    ResetGenerationIdMsg genIdMessage = null;
    if (generationIdNewValue == null)
    {
      genIdMessage = new ResetGenerationIdMsg(this.getGenerationID());
    }
    else
    {
      genIdMessage = new ResetGenerationIdMsg(generationIdNewValue);
    }
    broker.publish(genIdMessage);
  }
  /*
   ******** End of The total Update code *********
   */
  /*
   ******* Start of Monitoring Code **********
   */
  /**
   * Get the maximum receive window size.
   *
   * @return The maximum receive window size.
   */
  int getMaxRcvWindow()
  {
    if (broker != null)
      return broker.getMaxRcvWindow();
    else
      return 0;
  }
  /**
   * Get the current receive window size.
   *
   * @return The current receive window size.
   */
  int getCurrentRcvWindow()
  {
    if (broker != null)
      return broker.getCurrentRcvWindow();
    else
      return 0;
  }
  /**
   * Get the maximum send window size.
   *
   * @return The maximum send window size.
   */
  int getMaxSendWindow()
  {
    if (broker != null)
      return broker.getMaxSendWindow();
    else
      return 0;
  }
  /**
   * Get the current send window size.
   *
   * @return The current send window size.
   */
  int getCurrentSendWindow()
  {
    if (broker != null)
      return broker.getCurrentSendWindow();
    else
      return 0;
  }
  /**
   * Get the number of times the replication connection was lost.
   * @return The number of times the replication connection was lost.
   */
  int getNumLostConnections()
  {
    if (broker != null)
      return broker.getNumLostConnections();
    else
      return 0;
  }
  /**
   * Determine whether the connection to the replication server is encrypted.
   * @return true if the connection is encrypted, false otherwise.
   */
  boolean isSessionEncrypted()
  {
    if (broker != null)
      return broker.isSessionEncrypted();
    else
      return false;
  }
  /**
   * This method is called when the ReplicationDomain has completed the
   * processing of a received update synchronously.
   * In such cases the processUpdateDone () is called and the state
   * is updated automatically.
   *
   * @param msg The UpdateMessage that was processed.
   */
  void processUpdateDoneSynchronous(UpdateMsg msg)
  {
    // Warning: in synchronous mode, no way to tell the replay of an update went
    // wrong Just put null in processUpdateDone so that if assured replication
    // is used the ack is sent without error at replay flag.
    processUpdateDone(msg, null);
    state.update(msg.getChangeNumber());
  }
  /**
   * Check if the domain is connected to a ReplicationServer.
   *
   * @return true if the server is connected, false if not.
   */
  public boolean isConnected()
  {
    if (broker != null)
      return broker.isConnected();
    else
      return false;
  }
  /**
   * Get the name of the replicationServer to which this domain is currently
   * connected.
   *
   * @return the name of the replicationServer to which this domain
   *         is currently connected.
   */
  public String getReplicationServer()
  {
    if (broker != null)
      return broker.getReplicationServer();
    else
      return "Not connected";
  }
  /**
   * Gets the number of updates sent in assured safe read mode.
   * @return The number of updates sent in assured safe read mode.
   */
  public int getAssuredSrSentUpdates()
  {
    return assuredSrSentUpdates.get();
  }
  /**
   * Gets the number of updates sent in assured safe read mode that have been
   * acknowledged without errors.
   * @return The number of updates sent in assured safe read mode that have been
   * acknowledged without errors.
   */
  public int getAssuredSrAcknowledgedUpdates()
  {
    return assuredSrAcknowledgedUpdates.get();
  }
  /**
   * Gets the number of updates sent in assured safe read mode that have not
   * been acknowledged.
   * @return The number of updates sent in assured safe read mode that have not
   * been acknowledged.
   */
  public int getAssuredSrNotAcknowledgedUpdates()
  {
    return assuredSrNotAcknowledgedUpdates.get();
  }
  /**
   * Gets the number of updates sent in assured safe read mode that have not
   * been acknowledged due to timeout error.
   * @return The number of updates sent in assured safe read mode that have not
   * been acknowledged due to timeout error.
   */
  public int getAssuredSrTimeoutUpdates()
  {
    return assuredSrTimeoutUpdates.get();
  }
  /**
   * Gets the number of updates sent in assured safe read mode that have not
   * been acknowledged due to wrong status error.
   * @return The number of updates sent in assured safe read mode that have not
   * been acknowledged due to wrong status error.
   */
  public int getAssuredSrWrongStatusUpdates()
  {
    return assuredSrWrongStatusUpdates.get();
  }
  /**
   * Gets the number of updates sent in assured safe read mode that have not
   * been acknowledged due to replay error.
   * @return The number of updates sent in assured safe read mode that have not
   * been acknowledged due to replay error.
   */
  public int getAssuredSrReplayErrorUpdates()
  {
    return assuredSrReplayErrorUpdates.get();
  }
  /**
   * Gets the number of updates sent in assured safe read mode that have not
   * been acknowledged per server.
   * @return The number of updates sent in assured safe read mode that have not
   * been acknowledged per server.
   */
  public Map<Short, Integer> getAssuredSrServerNotAcknowledgedUpdates()
  {
    return assuredSrServerNotAcknowledgedUpdates;
  }
  /**
   * Gets the number of updates sent in assured safe data mode.
   * @return The number of updates sent in assured safe data mode.
   */
  public int getAssuredSdSentUpdates()
  {
    return assuredSdSentUpdates.get();
  }
  /**
   * Gets the number of updates sent in assured safe data mode that have been
   * acknowledged without errors.
   * @return The number of updates sent in assured safe data mode that have been
   * acknowledged without errors.
   */
  public int getAssuredSdAcknowledgedUpdates()
  {
    return assuredSdAcknowledgedUpdates.get();
  }
  /**
   * Gets the number of updates sent in assured safe data mode that have not
   * been acknowledged due to timeout error.
   * @return The number of updates sent in assured safe data mode that have not
   * been acknowledged due to timeout error.
   */
  public int getAssuredSdTimeoutUpdates()
  {
    return assuredSdTimeoutUpdates.get();
  }
  /**
   * Gets the number of updates sent in assured safe data mode that have not
   * been acknowledged due to timeout error per server.
   * @return The number of updates sent in assured safe data mode that have not
   * been acknowledged due to timeout error per server.
   */
  public Map<Short, Integer> getAssuredSdServerTimeoutUpdates()
  {
    return assuredSdServerTimeoutUpdates;
  }
  /**
   * Gets the date of the last status change.
   * @return The date of the last status change.
   */
  public Date getLastStatusChangeDate()
  {
    return lastStatusChangeDate;
  }
  /**
   * Resets the values of the monitoring counters for assured replication.
   */
  private void resetAssuredMonitoringCounters()
  {
    assuredSrSentUpdates = new AtomicInteger(0);
    assuredSrAcknowledgedUpdates = new AtomicInteger(0);
    assuredSrNotAcknowledgedUpdates = new AtomicInteger(0);
    assuredSrTimeoutUpdates = new AtomicInteger(0);
    assuredSrWrongStatusUpdates = new AtomicInteger(0);
    assuredSrReplayErrorUpdates = new AtomicInteger(0);
    assuredSrServerNotAcknowledgedUpdates = new HashMap<Short,Integer>();
    assuredSdSentUpdates = new AtomicInteger(0);
    assuredSdAcknowledgedUpdates = new AtomicInteger(0);
    assuredSdTimeoutUpdates = new AtomicInteger(0);
    assuredSdServerTimeoutUpdates = new HashMap<Short,Integer>();
  }
  /*
   ********** End of Monitoring Code **************
   */
  /**
   * Start the publish mechanism of the Replication Service.
   * After this method has been called, the publish service can be used
   * by calling the {@link #publish(UpdateMsg)} method.
   *
   * @param replicationServers   The replication servers that should be used.
   * @param window               The window size of this replication domain.
   * @param heartbeatInterval    The heartbeatInterval that should be used
   *                             to check the availability of the replication
   *                             servers.
   *
   * @throws ConfigException     If the DirectoryServer configuration was
   *                             incorrect.
   */
  public void startPublishService(
      Collection<String> replicationServers, int window,
      long heartbeatInterval) throws ConfigException
  {
    /*
     * create the broker object used to publish and receive changes
     */
    broker = new ReplicationBroker(
        this, state, serviceID,
        serverID, window,
        getGenerationID(),
        heartbeatInterval,
        new ReplSessionSecurity(),
        getGroupId());
    broker.start(replicationServers);
   /*
    * Create a replication monitor object responsible for publishing
    * monitoring information below cn=monitor.
    */
   monitor = new ReplicationMonitor(this);
   DirectoryServer.registerMonitorProvider(monitor);
  }
  /**
   * Starts the receiver side of the Replication Service.
   * <p>
   * After this method has been called, the Replication Service will start
   * calling the {@link #processUpdate(UpdateMsg)}.
   */
  public void startListenService()
  {
    // Create the listener thread
    listenerThread = new ListenerThread(this);
    listenerThread.start();
  }
  /**
   * Temporarily disable the Replication Service.
   * The Replication Service can be enabled again using
   * {@link #enableService()}.
   * <p>
   * It can be useful to disable the Replication Service when the
   * repository where the replicated information is stored becomes
   * temporarily unavailable and replicated updates can therefore not
   * be replayed during a while.
   */
  public void disableService()
  {
    // Stop the listener thread
    if (listenerThread != null)
    {
      listenerThread.shutdown();
    }
    if (broker != null)
    {
      broker.stop();
    }
    // Wait for the listener thread to stop
    if (listenerThread != null)
      listenerThread.waitForShutdown();
  }
  /**
   * Restart the Replication service after a {@link #disableService()}.
   * <p>
   * The Replication Service will restart from the point indicated by the
   * {@link ServerState} that was given as a parameter to the
   * {@link #startPublishService(Collection, ServerState, int, long)}
   * at startup time.
   * If some data have changed in the repository during the period of time when
   * the Replication Service was disabled, this {@link ServerState} should
   * therefore be updated by the Replication Domain subclass before calling
   * this method.
   */
  public void enableService()
  {
    broker.start();
    // Create the listener thread
    listenerThread = new ListenerThread(this);
    listenerThread.start();
  }
  /**
   * Definitively stops the Replication Service.
   */
  public void stopDomain()
  {
    DirectoryServer.deregisterMonitorProvider(monitor.getMonitorInstanceName());
    disableService();
    domains.remove(serviceID);
  }
  /**
   * Change the ReplicationDomain parameters.
   *
   * @param replicationServers  The new list of Replication Servers that this
   *                           domain should now use.
   * @param windowSize         The window size that this domain should use.
   * @param heartbeatInterval  The heartbeatInterval that this domain should
   *                           use.
   */
  public void changeConfig(
      Collection<String> replicationServers,
      int windowSize,
      long heartbeatInterval)
  {
    if (broker != null)
    {
      if (broker.changeConfig(
          replicationServers, windowSize, heartbeatInterval))
      {
        disableService();
        enableService();
      }
    }
  }
  /**
   * This method should trigger an export of the replicated data.
   * to the provided outputStream.
   * When finished the outputStream should be flushed and closed.
   *
   * @param output               The OutputStream where the export should
   *                             be produced.
   * @throws DirectoryException  When needed.
   */
  abstract protected void exportBackend(OutputStream output)
           throws DirectoryException;
  /**
   * This method should trigger an import of the replicated data.
   *
   * @param input                The InputStream from which
   *                             the import should be reading entries.
   *
   * @throws DirectoryException  When needed.
   */
  abstract protected void importBackend(InputStream input)
           throws DirectoryException;
  /**
   * This method should return the total number of objects in the
   * replicated domain.
   * This count will be used for reporting.
   *
   * @throws DirectoryException when needed.
   *
   * @return The number of objects in the replication domain.
   */
  public abstract long countEntries() throws DirectoryException;
  /**
   * This method should handle the processing of {@link UpdateMsg} receive
   * from remote replication entities.
   * <p>
   * This method will be called by a single thread and should therefore
   * should not be blocking.
   *
   * @param updateMsg The {@link UpdateMsg} that was received.
   *
   * @return A boolean indicating if the processing is completed at return
   *                   time.
   *                   If <code> true </code> is returned, no further
   *                   processing is necessary.
   *                   If <code> false </code> is returned, the subclass should
   *                   call the method
   *                   {@link #processUpdateDone(UpdateMsg)}
   *                   and update the ServerState
   *                   When this processing is complete.
   *
   */
  public abstract boolean processUpdate(UpdateMsg updateMsg);
  /**
   * This method must be called after each call to
   * {@link #processUpdate(UpdateMsg)} when the processing of the update is
   * completed.
   * <p>
   * It is useful for implementation needing to process the update in an
   * asynchronous way or using several threads, but must be called even
   * by implementation doing it in a synchronous, single-threaded way.
   *
   * @param  msg The UpdateMsg whose processing was completed.
   * @param replayErrorMsg if not null, this means an error occurred during the
   * replay of this update, and this is the matching human readable message
   * describing the problem.
   */
  public void processUpdateDone(UpdateMsg msg, String replayErrorMsg)
  {
    broker.updateWindowAfterReplay();
    // Send an ack if it was requested and the group id is the same of the RS
    // one. Only Safe Read mode makes sense in DS for returning an ack.
    byte rsGroupId = broker.getRsGroupId();
    if (msg.isAssured())
    {
      // Assured feature is supported starting from replication protocol V2
      if (broker.getProtocolVersion() >=
        ProtocolVersion.REPLICATION_PROTOCOL_V2)
      {
        AssuredMode msgAssuredMode = msg.getAssuredMode();
        if (msgAssuredMode == AssuredMode.SAFE_READ_MODE)
        {
          if (rsGroupId == groupId)
          {
            // Send the ack
            AckMsg ackMsg = new AckMsg(msg.getChangeNumber());
            if (replayErrorMsg != null)
            {
              // Mark the error in the ack
              //   -> replay error occured
              ackMsg.setHasReplayError(true);
              //   -> replay error occured in our server
              List<Short> idList = new ArrayList<Short>();
              idList.add(serverID);
              ackMsg.setFailedServers(idList);
            }
            broker.publish(ackMsg);
          }
        } else if (assuredMode != AssuredMode.SAFE_DATA_MODE)
        {
          Message errorMsg = ERR_DS_UNKNOWN_ASSURED_MODE.get(
            Short.toString(serverID), msgAssuredMode.toString(), serviceID,
            msg.toString());
          logError(errorMsg);
        } else
        {
          // In safe data mode assured update that comes up to a DS requires no
          // ack from a destinator DS. Safe data mode is based on RS acks only
        }
      }
    }
    incProcessedUpdates();
  }
  /**
   * Publish an {@link UpdateMsg} to the Replication Service.
   * <p>
   * The Replication Service will handle the delivery of this {@link UpdateMsg}
   * to all the participants of this Replication Domain.
   * These members will be receive this {@link UpdateMsg} through a call
   * of the {@link #processUpdate(UpdateMsg)} message.
   *
   * @param msg The UpdateMsg that should be pushed.
   * @throws TimeoutException When assured replication is enabled and the
   * configured timeout occurs when blocked waiting for the ack.
   */
  public void publish(UpdateMsg msg) throws TimeoutException
  {
    byte rsGroupId = broker.getRsGroupId();
    // If assured configured, set message accordingly to request an ack in the
    // right assured mode.
    // No ack requested for a RS with a different group id. Assured replication
    // suported for the same locality, i.e: a topology working in the same
    // geographical location). If we are connected to a RS which is not in our
    // locality, no need to ask for an ack.
    if ( assured && ( rsGroupId == groupId ) )
    {
      msg.setAssured(true);
      msg.setAssuredMode(assuredMode);
      if (assuredMode == AssuredMode.SAFE_DATA_MODE)
        msg.setSafeDataLevel(assuredSdLevel);
      // Add the assured message to the list of update that are
      // waiting for acks
      synchronized (waitingAckMsgs)
      {
        waitingAckMsgs.put(msg.getChangeNumber(), msg);
      }
    }
    // Publish the update
    broker.publish(msg);
    state.update(msg.getChangeNumber());
    numSentUpdates.incrementAndGet();
    // If assured mode configured, wait for acknowledgement for the just sent
    // message
    if ( assured && ( rsGroupId == groupId ) )
    {
      // Increment assured replication monitoring counters
      switch(assuredMode)
      {
        case SAFE_READ_MODE:
          assuredSrSentUpdates.incrementAndGet();
          break;
        case SAFE_DATA_MODE:
          assuredSdSentUpdates.incrementAndGet();
          break;
        default:
          // Should no happen
      }
      // Now wait for ack matching the sent assured update
      waitForAck(msg);
    }
  }
  /**
   * Publish informations to the Replication Service (not assured mode).
   *
   * @param msg  The byte array containing the informations that should
   *             be sent to the remote entities.
   */
  public void publish(byte[] msg)
  {
    UpdateMsg update = new UpdateMsg(generator.newChangeNumber(), msg);
    try
    {
      publish(update);
    } catch (TimeoutException e)
    {
      // Should never happen as assured mode not requested
    }
  }
  /**
   * This method should return the generationID to use for this
   * ReplicationDomain.
   * This method can be called at any time after the ReplicationDomain
   * has been started.
   *
   * @return The GenerationID.
   */
  public abstract long getGenerationID();
  /**
   * Subclasses should use this method to add additional monitoring
   * information in the ReplicationDomain.
   *
   * @return Additional monitoring attributes that will be added in the
   *         ReplicationDomain monitoring entry.
   */
  public Collection<Attribute> getAdditionalMonitoring()
  {
    return new ArrayList<Attribute>();
  }
  /**
   * Returns a boolean indicating if a total update import is currently
   * in Progress.
   *
   * @return A boolean indicating if a total update import is currently
   *         in Progress.
   */
  boolean importInProgress()
  {
    if (ieContext == null)
      return false;
    else
      return ieContext.importInProgress;
  }
  /**
   * Returns a boolean indicating if a total update export is currently
   * in Progress.
   *
   * @return A boolean indicating if a total update export is currently
   *         in Progress.
   */
  boolean exportInProgress()
  {
    if (ieContext == null)
      return false;
    else
      return !ieContext.importInProgress;
  }
  /**
   * Returns the number of entries still to be processed when a total update
   * is in progress.
   *
   * @return The number of entries still to be processed when a total update
   *         is in progress.
   */
  long getLeftEntryCount()
  {
    if (ieContext != null)
      return ieContext.entryLeftCount;
    else
      return 0;
  }
  /**
   * Returns the total number of entries to be processed when a total update
   * is in progress.
   *
   * @return The total number of entries to be processed when a total update
   *         is in progress.
   */
  long getTotalEntryCount()
  {
    if (ieContext != null)
      return ieContext.entryCount;
    else
      return 0;
  }
}
opends/src/server/org/opends/server/replication/service/ReplicationMonitor.java
New file
@@ -0,0 +1,331 @@
/*
 * 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-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.service;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Map;
import org.opends.server.admin.std.server.MonitorProviderCfg;
import org.opends.server.api.MonitorProvider;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.Attributes;
/**
 * Class used to generate monitoring information for the replication.
 */
public class ReplicationMonitor extends MonitorProvider<MonitorProviderCfg>
{
  private ReplicationDomain domain;  // the replication plugin
  /**
   * Create a new replication monitor.
   * @param domain the plugin which created the monitor
   */
  public ReplicationMonitor(ReplicationDomain domain)
  {
    super("Replication monitor " + domain.getServiceID()
        + " " + domain.getServerId());
    this.domain = domain;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeMonitorProvider(MonitorProviderCfg configuration)
  {
    // no implementation needed.
  }
  /**
   * 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 "Replication Domain "  + domain.getServiceID()
       + " " + domain.getServerId();
  }
  /**
   * 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>();
    /* get the base dn */
    Attribute attr = Attributes.create("base-dn", domain.getServiceID());
    attributes.add(attr);
    /* get the base dn */
    attr = Attributes.create("connected-to", domain
        .getReplicationServer());
    attributes.add(attr);
    /* get number of lost connections */
    addMonitorData(attributes, "lost-connections",
                   domain.getNumLostConnections());
    /* get number of received updates */
    addMonitorData(attributes, "received-updates", domain.getNumRcvdUpdates());
    /* get number of updates sent */
    addMonitorData(attributes, "sent-updates", domain.getNumSentUpdates());
    /* get number of changes replayed */
    addMonitorData(attributes, "replayed-updates",
                   domain.getNumProcessedUpdates());
    /* get server-id */
    addMonitorData(attributes, "server-id",
                   domain.getServerId());
    /* get window information */
    addMonitorData(attributes, "max-rcv-window", domain.getMaxRcvWindow());
    addMonitorData(attributes, "current-rcv-window",
                               domain.getCurrentRcvWindow());
    addMonitorData(attributes, "max-send-window",
                               domain.getMaxSendWindow());
    addMonitorData(attributes, "current-send-window",
                               domain.getCurrentSendWindow());
    /* get the Server State */
    final String ATTR_SERVER_STATE = "server-state";
    AttributeType type =
      DirectoryServer.getDefaultAttributeType(ATTR_SERVER_STATE);
    AttributeBuilder builder = new AttributeBuilder(type, ATTR_SERVER_STATE);
    for (String str : domain.getServerState().toStringSet())
    {
      builder.add(new AttributeValue(type,str));
    }
    attributes.add(builder.toAttribute());
    attributes.add(Attributes.create("ssl-encryption",
        String.valueOf(domain.isSessionEncrypted())));
    attributes.add(Attributes.create("generation-id",
        String.valueOf(domain.getGenerationID())));
    /*
     * Add import/export monitoring attribute
     */
    if (domain.importInProgress())
    {
      addMonitorData(attributes, "total-update", "import");
      addMonitorData(
          attributes, "total-update-entry-count", domain.getTotalEntryCount());
      addMonitorData(
          attributes, "total-update-entry-left", domain.getLeftEntryCount());
    }
    if (domain.exportInProgress())
    {
      addMonitorData(attributes, "total-update", "export");
      addMonitorData(
          attributes, "total-update-entry-count", domain.getTotalEntryCount());
      addMonitorData(
          attributes, "total-update-entry-left", domain.getLeftEntryCount());
    }
    /* Add the concrete Domain attributes */
    Collection<Attribute> additionalMonitoring =
      domain.getAdditionalMonitoring();
    attributes.addAll(additionalMonitoring);
    /*
     * Add assured replication related monitoring fields
     * (see domain.getXXX() method comment for field meaning)
     */
    addMonitorData(attributes, "assured-sr-sent-updates",
      domain.getAssuredSrSentUpdates());
    addMonitorData(attributes, "assured-sr-acknowledged-updates",
      domain.getAssuredSrAcknowledgedUpdates());
    addMonitorData(attributes, "assured-sr-not-acknowledged-updates",
      domain.getAssuredSrNotAcknowledgedUpdates());
    addMonitorData(attributes, "assured-sr-timeout-updates",
      domain.getAssuredSrTimeoutUpdates());
    addMonitorData(attributes, "assured-sr-wrong-status-updates",
      domain.getAssuredSrWrongStatusUpdates());
    addMonitorData(attributes, "assured-sr-replay-error-updates",
      domain.getAssuredSrReplayErrorUpdates());
    final String ATTR_ASS_SR_SRV = "assured-sr-server-not-acknowledged-updates";
    type = DirectoryServer.getDefaultAttributeType(ATTR_ASS_SR_SRV);
    builder = new AttributeBuilder(type, ATTR_ASS_SR_SRV);
    Map<Short, Integer> srSrvNotAckUps =
      domain.getAssuredSrServerNotAcknowledgedUpdates();
    if (srSrvNotAckUps.size() > 0)
    {
      for (Short serverId : srSrvNotAckUps.keySet())
      {
        String str = serverId + ":" + srSrvNotAckUps.get(serverId);
        builder.add(new AttributeValue(type, str));
      }
      attributes.add(builder.toAttribute());
    }
    addMonitorData(attributes, "assured-sd-sent-updates",
      domain.getAssuredSdSentUpdates());
    addMonitorData(attributes, "assured-sd-acknowledged-updates",
      domain.getAssuredSdAcknowledgedUpdates());
    addMonitorData(attributes, "assured-sd-timeout-updates",
      domain.getAssuredSdTimeoutUpdates());
    final String ATTR_ASS_SD_SRV = "assured-sd-server-timeout-updates";
    type = DirectoryServer.getDefaultAttributeType(ATTR_ASS_SD_SRV);
    builder = new AttributeBuilder(type, ATTR_ASS_SD_SRV);
    Map<Short, Integer> sdSrvTimUps =
      domain.getAssuredSdServerTimeoutUpdates();
    if (sdSrvTimUps.size() > 0)
    {
      for (Short serverId : sdSrvTimUps.keySet())
      {
        String str = serverId + ":" + sdSrvTimUps.get(serverId);
        builder.add(new AttributeValue(type, str));
      }
      attributes.add(builder.toAttribute());
    }
    /*
     * Status related monitoring fields
     */
    addMonitorData(attributes, "last-status-change-date",
      domain.getLastStatusChangeDate().toString());
    addMonitorData(attributes, "status", domain.getStatus().toString());
    return attributes;
  }
  /**
   * Add an attribute with an integer value to the list of monitoring
   * attributes.
   *
   * @param attributes the list of monitoring attributes
   * @param name the name of the attribute to add.
   * @param value The integer value of he attribute to add.
   */
  public static void addMonitorData(
      ArrayList<Attribute> attributes,
      String name,
      int value)
  {
    AttributeType type = DirectoryServer.getDefaultAttributeType(name);
    attributes.add(Attributes.create(type, new AttributeValue(type,
        String.valueOf(value))));
  }
  /**
   * Add an attribute with an integer value to the list of monitoring
   * attributes.
   *
   * @param attributes the list of monitoring attributes
   * @param name the name of the attribute to add.
   * @param value The integer value of he attribute to add.
   */
  public static void addMonitorData(
      ArrayList<Attribute> attributes,
      String name,
      long value)
  {
    AttributeType type = DirectoryServer.getDefaultAttributeType(name);
    attributes.add(Attributes.create(type, new AttributeValue(type,
        String.valueOf(value))));
  }
  /**
   * Add an attribute with an integer value to the list of monitoring
   * attributes.
   *
   * @param attributes the list of monitoring attributes
   * @param name the name of the attribute to add.
   * @param value The String value of he attribute to add.
   */
  public static void addMonitorData(
      ArrayList<Attribute> attributes,
      String name,
      String value)
  {
    AttributeType type = DirectoryServer.getDefaultAttributeType(name);
    attributes.add(Attributes.create(type, new AttributeValue(type, value)));
  }
  /**
   * 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
  }
}
opends/src/server/org/opends/server/replication/service/SetGenerationIdTask.java
File was renamed from opends/src/server/org/opends/server/tasks/SetGenerationIdTask.java
@@ -24,10 +24,13 @@
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.tasks;
package org.opends.server.replication.service;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.core.DirectoryServer.getAttributeType;
import org.opends.server.tasks.TaskUtils;
import java.util.List;
import org.opends.messages.MessageBuilder;
@@ -37,10 +40,8 @@
import org.opends.server.backends.task.TaskState;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
@@ -56,14 +57,9 @@
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  boolean isCompressed            = false;
  boolean isEncrypted             = false;
  boolean skipSchemaValidation    = false;
  String  domainString            = null;
  ReplicationDomain domain        = null;
  TaskState initState;
  Long generationId = null;
  private String  domainString            = null;
  private ReplicationDomain domain        = null;
  private Long generationId = null;
  private static final void debugInfo(String s)
  {
@@ -124,22 +120,18 @@
    attrList = taskEntry.getAttribute(typeDomainBase);
    domainString = TaskUtils.getSingleValueString(attrList);
    DN domainDN = DN.nullDN();
    try
    {
      domainDN = DN.decode(domainString);
      domain = ReplicationDomain.retrievesReplicationDomain(domainString);
    }
    catch(Exception e)
    catch(DirectoryException e)
    {
      MessageBuilder mb = new MessageBuilder();
      mb.append(TaskMessages.ERR_TASK_INITIALIZE_INVALID_DN.get());
      mb.append(e.getMessage());
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX,
          mb.toMessage());
      throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, e);
    }
    domain = ReplicationDomain.retrievesReplicationDomain(domainDN);
  }
  /**
@@ -148,7 +140,7 @@
  protected TaskState runTask()
  {
    debugInfo("setGenerationIdTask is starting on domain%s" +
        domain.getBaseDN());
        domain.getServiceID());
    try
    {
opends/src/server/org/opends/server/replication/service/package-info.java
File was renamed from opends/src/server/org/opends/server/replication/server/AckMessageListComparator.java
@@ -22,22 +22,20 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.server;
import java.util.Comparator;
/**
 * This comparator is used to build TreeSet of AckMessageLists.
 * This package contains the generic of the Multi-Master
 * replication code that works on the Directory Server side.
 * <br>
 * Developers planning to use the Replication Service should
 * subClass the <A> HREF="ReplicationDomain.html"><B>ReplicationDomain</B></A>
 * class
 */
public class AckMessageListComparator implements Comparator<AckMessageList>
{
  /**
   * {@inheritDoc}
   */
  public int compare(AckMessageList a1, AckMessageList a2)
  {
    return a1.getChangeNumber().compareTo(a2.getChangeNumber());
  }
}
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.PRIVATE)
package org.opends.server.replication.service;
opends/src/server/org/opends/server/tools/dsreplication/ReplicationCliMain.java
@@ -6129,7 +6129,7 @@
    oc.add("ds-task-reset-generation-id");
    attrs.put(oc);
    attrs.put("ds-task-class-name",
        "org.opends.server.tasks.SetGenerationIdTask");
        "org.opends.server.replication.service.SetGenerationIdTask");
    if (isPre)
    {
      if (!localOnly)
@@ -6300,7 +6300,7 @@
    oc.add("ds-task-initialize-remote-replica");
    attrs.put(oc);
    attrs.put("ds-task-class-name",
        "org.opends.server.tasks.InitializeTargetTask");
        "org.opends.server.replication.service.InitializeTargetTask");
    attrs.put("ds-task-initialize-domain-dn", baseDN);
    attrs.put("ds-task-initialize-replica-server-id", "all");
    while (!taskCreated)
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ChangeNumberControlPluginTestCase.java
@@ -31,16 +31,12 @@
import java.io.FileReader;
import java.net.ServerSocket;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.ReplicationTestCase;
import org.testng.annotations.DataProvider;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.opends.server.tools.LDAPModify;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import static org.opends.server.util.ServerConstants.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
@@ -53,19 +49,17 @@
   * The port of the replicationServer.
   */
  private int replServerPort;
  /**
   * The replicationServer that will be used in this test.
   */
  private static final int WINDOW_SIZE = 10;
  private static final int REPLICATION_QUEUE_SIZE = 100;
  private DN baseDn;
  /**
   * Before starting the tests, start the server and configure a
   * replicationServer.
   */
  @BeforeClass(alwaysRun=true)
  public void setUp() throws Exception {
    super.setUp();
@@ -80,9 +74,9 @@
    // replication server
    String replServerLdif =
        "dn: cn=Replication Server, " + SYNCHRO_PLUGIN_DN + "\n"
        + "objectClass: top\n"
        + "objectClass: top\n"
        + "objectClass: ds-cfg-replication-server\n"
        + "cn: Replication Server\n"
        + "cn: Replication Server\n"
        + "ds-cfg-replication-port: " + replServerPort + "\n"
        + "ds-cfg-replication-db-directory: ChangeNumberControlDbTest\n"
        + "ds-cfg-replication-server-id: 103\n";
@@ -128,7 +122,7 @@
         + "changetype: delete"}
    };
  }
  @Test(dataProvider="operations")
  public void ChangeNumberControlTest(String request) throws Exception {
@@ -146,13 +140,13 @@
    };
    String resultPath = TestCaseUtils.createTempFile();
    FileOutputStream fos = new FileOutputStream(resultPath);
    assertEquals(LDAPModify.mainModify(args, false, fos, System.err), 0);
    //fos.flush();
    fos.close();
    assertTrue(isCsnLinePresent(resultPath));
  }
@@ -168,27 +162,5 @@
    }
    return (found);
  }
  /**
   * Utility function. Can be used to create and add and entry
   * in the local DS from its ldif description.
   *
   * @param entryString  The entry in ldif from.
   * @return             The ResultCode of the operation.
   * @throws Exception   If something went wrong.
   */
  private ResultCode addEntry(String entryString) throws Exception
  {
    Entry entry;
    AddOperationBasis addOp;
    entry = TestCaseUtils.entryFromLdifString(entryString);
    addOp = new AddOperationBasis(InternalClientConnection.getRootConnection(),
       InternalClientConnection.nextOperationID(), InternalClientConnection
       .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
       entry.getUserAttributes(), entry.getOperationalAttributes());
    addOp.setInternalOperation(true);
    addOp.run();
    return addOp.getResultCode();
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/DependencyTest.java
@@ -39,11 +39,11 @@
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.backends.MemoryBackend;
import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.plugin.DomainFakeCfg;
import org.opends.server.replication.plugin.MultimasterReplication;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.replication.protocol.ModifyDNMsg;
@@ -90,7 +90,7 @@
  public void addModDelDependencyTest() throws Exception
  {
    ReplicationServer replServer = null;
    ReplicationDomain domain = null;
    LDAPReplicationDomain domain = null;
    DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    SynchronizationProvider replicationPlugin = null;
    short brokerId = 2;
@@ -255,7 +255,7 @@
  public void moddnDelDependencyTest() throws Exception
  {
    ReplicationServer replServer = null;
    ReplicationDomain domain = null;
    LDAPReplicationDomain domain = null;
    DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    SynchronizationProvider replicationPlugin = null;
    short brokerId = 2;
@@ -302,7 +302,7 @@
      Thread.sleep(2000);
      domain = MultimasterReplication.createNewDomain(domainConf);
      replicationPlugin.completeSynchronizationProvider();
      ReplicationBroker broker =
        openReplicationSession(baseDn, brokerId, 1000, replServerPort, 1000,
                               false, CLEAN_DB_GENERATION_ID);
@@ -330,7 +330,7 @@
      assertTrue(found, "The initial entry add failed");
      // disable the domain to make sure that the messages are
      // disable the domain to make sure that the messages are
      // all sent in a row.
      domain.disable();
@@ -413,7 +413,7 @@
  public void addDelAddDependencyTest() throws Exception
  {
    ReplicationServer replServer = null;
    ReplicationDomain domain = null;
    LDAPReplicationDomain domain = null;
    DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    SynchronizationProvider replicationPlugin = null;
    short brokerId = 2;
@@ -546,7 +546,7 @@
  public void addModdnDependencyTest() throws Exception
  {
    ReplicationServer replServer = null;
    ReplicationDomain domain = null;
    LDAPReplicationDomain domain = null;
    DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    SynchronizationProvider replicationPlugin = null;
    short brokerId = 2;
@@ -555,7 +555,7 @@
    int AddSequenceLength = 30;
    cleanDB();
    try
    {
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/GenerationIdTest.java
@@ -54,10 +54,10 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.common.ServerStatus;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.ChangeStatusMsg;
import org.opends.server.replication.protocol.DoneMsg;
@@ -66,7 +66,6 @@
import org.opends.server.replication.protocol.InitializeTargetMsg;
import org.opends.server.replication.protocol.ReplicationMsg;
import org.opends.server.replication.protocol.SocketSession;
import org.opends.server.replication.protocol.TopologyMsg;
import org.opends.server.replication.server.ReplServerFakeConfiguration;
import org.opends.server.replication.server.ReplicationBackend;
import org.opends.server.replication.server.ReplicationServer;
@@ -118,7 +117,7 @@
  private ReplicationServer replServer2 = null;
  private ReplicationServer replServer3 = null;
  private boolean emptyOldChanges = true;
  ReplicationDomain replDomain = null;
  LDAPReplicationDomain replDomain = null;
  private Entry taskInitRemoteS2;
  SocketSession ssSession = null;
  boolean ssShutdownRequested = false;
@@ -210,7 +209,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-initialize-remote-replica",
        "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask",
        "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask",
        "ds-task-initialize-domain-dn: " + baseDn,
        "ds-task-initialize-replica-server-id: " + server2ID);
  }
@@ -440,7 +439,7 @@
        "Unable to add the synchronized server");
      configEntryList.add(synchroServerEntry.getDN());
      replDomain = ReplicationDomain.retrievesReplicationDomain(baseDn);
      replDomain = LDAPReplicationDomain.retrievesReplicationDomain(baseDn);
      if (replDomain != null)
@@ -668,7 +667,7 @@
          }
        }
      }
      assertEquals(searchOperation.getSearchEntries().size(), expectedCount);
      assertEquals(searchOperation.getSearchEntries().size(), expectedCount);
    }
    catch(Exception e)
    {
@@ -719,14 +718,14 @@
      debugInfo(testCase + " Expect genId to be set in memory on the replication " +
      " server side (not wrote on disk/db since no change occurred).");
      rgenId = replServer1.getGenerationId(baseDn);
      rgenId = replServer1.getGenerationId(baseDn.toNormalizedString());
      assertEquals(rgenId, EMPTY_DN_GENID);
      // Clean for next test
      debugInfo(testCase + " Unconfiguring DS1 to replicate to RS1(" + changelog1ID + ")");
      disconnectFromReplServer(changelog1ID);
      //===========================================================
      debugInfo(testCase + " ** TEST ** Non empty backend");
@@ -742,7 +741,7 @@
      assertTrue(genId != EMPTY_DN_GENID);
      debugInfo(testCase + " Test that the generationId is set on RS1");
      rgenId = replServer1.getGenerationId(baseDn);
      rgenId = replServer1.getGenerationId(baseDn.toNormalizedString());
      assertEquals(genId, rgenId);
      //===========================================================
@@ -771,22 +770,6 @@
        fail("Broker connection is expected to be accepted.");
      }
      // Broker 2 should receive an update topo message to signal broker 3
      // arrival in topology
      try
      {
        ReplicationMsg msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive a TopologyMsg."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS2 is expected to receive a TopologyMsg.");
      }
      //===========================================================
      debugInfo(testCase + " ** TEST ** DS2 (bad genID) changes must be ignored.");
@@ -826,7 +809,8 @@
      //===========================================================
      debugInfo(testCase + " ** TEST ** Persistence of the generation ID in RS1");
      long genIdBeforeShut = replServer1.getGenerationId(baseDn);
      long genIdBeforeShut =
        replServer1.getGenerationId(baseDn.toNormalizedString());
      debugInfo("Shutdown replServer1");
      broker2.stop();
@@ -847,7 +831,8 @@
      debugInfo("Delay to allow DS to reconnect to replServer1");
      long genIdAfterRestart = replServer1.getGenerationId(baseDn);
      long genIdAfterRestart =
        replServer1.getGenerationId(baseDn.toNormalizedString());
      debugInfo("Aft restart / replServer.genId=" + genIdAfterRestart);
      assertTrue(replServer1!=null, "Replication server creation failed.");
      assertTrue(genIdBeforeShut == genIdAfterRestart,
@@ -880,21 +865,6 @@
        fail("Broker connection is expected to be accepted.");
      }
      // Broker 2 should receive an update topo message to signal broker 3
      // arrival in topology
      try
      {
        ReplicationMsg msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive a TopologyMsg."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS2 is expected to receive a TopologyMsg.");
      }
      debugInfo("Launch on-line import on DS1");
      long oldGenId = genId;
@@ -903,69 +873,6 @@
      performLdifImport();
      connectServer1ToChangelog(changelog1ID);
      // Broker 2 and 3 should receive 2 update topo messages to signal broker 1
      // has disconnected from topology then reconnected
      try
      {
        ReplicationMsg msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive a first TopologyMsg" +
            " for Broker 1 import + reconnect."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS2 is expected to receive a first TopologyMsg for DS1 import + " +
          "reconnect.");
      }
      try
      {
        ReplicationMsg msg = broker3.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 3 connection is expected to receive a first TopologyMsg" +
            " for Broker 1 import + reconnect."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS3 is expected to receive a first TopologyMsg for DS1 import + " +
          "reconnect.");
      }
      try
      {
        ReplicationMsg msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive a second TopologyMsg" +
            " for Broker 1 import + reconnect."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS2 is expected to receive a second TopologyMsg for DS1 import + " +
          "reconnect.");
      }
      try
      {
        ReplicationMsg msg = broker3.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 3 connection is expected to receive a second TopologyMsg" +
            " for Broker 1 import + reconnect."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS3 is expected to receive a second TopologyMsg for DS1 import + " +
          "reconnect.");
      }
      debugInfo("Create Reset task on DS1 to propagate the new gen ID as the reference");
      Entry taskReset = TestCaseUtils.makeEntry(
          "dn: ds-task-id=resetgenid"+genId+ UUID.randomUUID() +
@@ -973,10 +880,10 @@
          "objectclass: top",
          "objectclass: ds-task",
          "objectclass: ds-task-reset-generation-id",
          "ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask",
          "ds-task-class-name: org.opends.server.replication.service.SetGenerationIdTask",
          "ds-task-reset-generation-id-domain-base-dn: " + baseDnStr);
      addTask(taskReset, ResultCode.SUCCESS, null);
      waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null);
      waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null);
      // Broker 2 and 3 should receive 1 change status message to order them
      // to enter the bad gen id status
@@ -1025,66 +932,6 @@
          "bad gen id status.");
      }
      // Broker 2 and 3 should receive 1 update topo message to signal other broker gen id
      // has been resetted, and 2 topo messages to signal DS1 has been disconnected
      // then reconnected
      try
      {
        ReplicationMsg msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive 1 TopologyMsg" +
            " for gen id reset of broker 3."
              + msg);
        }
        msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive 1 TopologyMsg" +
            " for DS1 disconnected."
              + msg);
        }
        msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive 1 TopologyMsg" +
            " for DS1 reconnected."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS2 is expected to receive 3 TopologyMsg for gen id reset.");
      }
      try
      {
        ReplicationMsg msg = broker3.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 3 connection is expected to receive 1 TopologyMsg" +
            " for gen id reset of broker 2."
              + msg);
        }
        msg = broker3.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 3 connection is expected to receive 1 TopologyMsg" +
            " for DS1 disconnected."
              + msg);
        }
        msg = broker3.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 3 connection is expected to receive 1 TopologyMsg" +
            " for DS1 reconnected"
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS3 is expected to receive 3 TopologyMsg for gen id reset.");
      }
      debugInfo("DS1 root entry must contain the new gen ID");
      genId = readGenIdFromSuffixRootEntry();
      assertTrue(genId != -1, "DS is expected to have a new genID computed " +
@@ -1093,13 +940,14 @@
        + "is expected to be diffrent from previous one");
      debugInfo("RS1 must have the new gen ID");
      rgenId = replServer1.getGenerationId(baseDn);
      rgenId = replServer1.getGenerationId(baseDn.toNormalizedString());
      assertEquals(genId, rgenId, "DS and replServer are expected to have same genId.");
      debugInfo("RS1 must have been cleared since it has not the proper generation ID");
      checkChangelogSize(0);
      assertTrue(!replServer1.getReplicationServerDomain(baseDn, false).
      assertTrue(!replServer1.getReplicationServerDomain(
          baseDn.toNormalizedString(), false).
          isDegradedDueToGenerationId(server1ID),
      "Expecting that DS1 status in RS1 is : not in bad gen id.");
@@ -1107,10 +955,12 @@
      debugInfo(testCase + " ** TEST ** Previous test set a new gen ID on the "+
          "topology, verify degradation of DS2 and DS3");
      assertTrue(replServer1.getReplicationServerDomain(baseDn, false).
      assertTrue(replServer1.getReplicationServerDomain(
          baseDn.toNormalizedString(), false).
          isDegradedDueToGenerationId(server2ID),
      "Expecting that DS2 with old gen ID is in bad gen id from RS1");
      assertTrue(replServer1.getReplicationServerDomain(baseDn, false).
      assertTrue(replServer1.getReplicationServerDomain(
          baseDn.toNormalizedString(), false).
          isDegradedDueToGenerationId(server3ID),
      "Expecting that DS3 with old gen ID is in bad gen id from RS1");
@@ -1173,35 +1023,6 @@
      broker3 = openReplicationSession(baseDn,
          server3ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges, genId);
      // Broker 2 should receive 2 update topo messages to signal broker 3
      // stopped and restarted
      try
      {
        ReplicationMsg msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive a first TopologyMsg."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS2 is expected to receive a first TopologyMsg.");
      }
      try
      {
        ReplicationMsg msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive a second TopologyMsg."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS2 is expected to receive a second TopologyMsg.");
      }
      debugInfo("Adding reset task to DS1");
      taskReset = TestCaseUtils.makeEntry(
          "dn: ds-task-id=resetgenid"+ UUID.randomUUID() +
@@ -1209,47 +1030,15 @@
          "objectclass: top",
          "objectclass: ds-task",
          "objectclass: ds-task-reset-generation-id",
          "ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask",
          "ds-task-class-name: org.opends.server.replication.service.SetGenerationIdTask",
          "ds-task-reset-generation-id-domain-base-dn: " + baseDnStr);
      addTask(taskReset, ResultCode.SUCCESS, null);
      waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null);
      Thread.sleep(200);
      // Broker 2 and 3 should not receive a change status as they are connected with
      // the right genid, but should anyway receive a topo message to update them with potential
      // updates.
      try
      {
        ReplicationMsg msg = broker2.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 2 connection is expected to receive 1 TopologyMsg" +
            " for topo update on reset gen id."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS2 is expected to receive 1 TopologyMsg for topo update on reset gen id.");
      }
      try
      {
        ReplicationMsg msg = broker3.receive();
        if (!(msg instanceof TopologyMsg))
        {
          fail("Broker 3 connection is expected to receive 1 TopologyMsg" +
            " for topo update on reset gen id."
              + msg);
        }
      }
      catch(SocketTimeoutException se)
      {
        fail("DS3 is expected to receive 1 TopologyMsg for topo update on reset gen id.");
      }
      debugInfo("Verify that RS1 has still the right genID");
      assertEquals(replServer1.getGenerationId(baseDn), rgenId);
      assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), rgenId);
      // Updates count in RS1 must stay unchanged = to 1
      Thread.sleep(500);
@@ -1257,13 +1046,15 @@
      debugInfo("Verifying that DS2 is not in bad gen id any more");
      assertTrue(!replServer1.getReplicationServerDomain(baseDn, false).
      assertTrue(!replServer1.getReplicationServerDomain(
          baseDn.toNormalizedString(), false).
          isDegradedDueToGenerationId(server2ID),
      "Expecting that DS2 is not in bad gen id from RS1");
      debugInfo("Verifying that DS3 is not in bad gen id any more");
      assertTrue(!replServer1.getReplicationServerDomain(baseDn, false).
      assertTrue(!replServer1.getReplicationServerDomain(
          baseDn.toNormalizedString(), false).
          isDegradedDueToGenerationId(server3ID),
      "Expecting that DS3 is not in bad gen id from RS1");
@@ -1281,7 +1072,7 @@
        /* expected */
        AddMsg rcvmsg = (AddMsg)msg;
        assertEquals(rcvmsg.getChangeNumber(), emsg.getChangeNumber());
      }
      }
      catch(SocketTimeoutException e)
      {
        fail("The msg send by DS2 is expected to be received by DS3)");
@@ -1339,11 +1130,11 @@
      Thread.sleep(1500);
      debugInfo("Expect genId are set in all replServers.");
      assertEquals(replServer1.getGenerationId(baseDn), EMPTY_DN_GENID,
      assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), EMPTY_DN_GENID,
        " in replServer1");
      assertEquals(replServer2.getGenerationId(baseDn), EMPTY_DN_GENID,
      assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), EMPTY_DN_GENID,
        " in replServer2");
      assertEquals(replServer3.getGenerationId(baseDn), EMPTY_DN_GENID,
      assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), EMPTY_DN_GENID,
        " in replServer3");
      debugInfo("Disconnect DS from replServer1.");
@@ -1352,9 +1143,9 @@
      debugInfo(
        "Expect genIds to be resetted in all servers to -1 as no more DS in topo");
      assertEquals(replServer1.getGenerationId(baseDn), -1);
      assertEquals(replServer2.getGenerationId(baseDn), -1);
      assertEquals(replServer3.getGenerationId(baseDn), -1);
      assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), -1);
      assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), -1);
      assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), -1);
      debugInfo("Add entries to DS");
      this.addTestEntriesToDB(updatedEntries);
@@ -1367,9 +1158,9 @@
        "Expect genIds to be set in all servers based on the added entries.");
      genId = readGenIdFromSuffixRootEntry();
      assertTrue(genId != -1);
      assertEquals(replServer1.getGenerationId(baseDn), genId);
      assertEquals(replServer2.getGenerationId(baseDn), genId);
      assertEquals(replServer3.getGenerationId(baseDn), genId);
      assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), genId);
      assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), genId);
      assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), genId);
      debugInfo("Connecting broker2 to replServer3 with a good genId");
      try
@@ -1385,7 +1176,7 @@
      debugInfo(
        "Expecting that broker2 is not in bad gen id since it has a correct genId");
      assertTrue(!replServer1.getReplicationServerDomain(baseDn, false).
      assertTrue(!replServer1.getReplicationServerDomain(baseDn.toNormalizedString(), false).
        isDegradedDueToGenerationId(server2ID));
      debugInfo("Disconnecting DS from replServer1");
@@ -1393,9 +1184,9 @@
      debugInfo(
        "Expect all genIds to keep their value since broker2 is still connected.");
      assertEquals(replServer1.getGenerationId(baseDn), genId);
      assertEquals(replServer2.getGenerationId(baseDn), genId);
      assertEquals(replServer3.getGenerationId(baseDn), genId);
      assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), genId);
      assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), genId);
      assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), genId);
      debugInfo("Connecting broker2 to replServer1 with a bad genId");
      try
@@ -1412,7 +1203,7 @@
      debugInfo(
        "Expecting that broker3 is in bad gen id since it has a bad genId");
      assertTrue(replServer1.getReplicationServerDomain(baseDn, false).
      assertTrue(replServer1.getReplicationServerDomain(baseDn.toNormalizedString(), false).
        isDegradedDueToGenerationId(server3ID));
      int found = testEntriesInDb();
@@ -1432,7 +1223,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-reset-generation-id",
        "ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask",
        "ds-task-class-name: org.opends.server.replication.service.SetGenerationIdTask",
        "ds-task-reset-generation-id-domain-base-dn: " + baseDnStr);
      addTask(taskReset, ResultCode.SUCCESS, null);
      waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null);
@@ -1440,9 +1231,9 @@
      debugInfo("Verifying that all replservers genIds have been reset.");
      genId = readGenIdFromSuffixRootEntry();
      assertEquals(replServer1.getGenerationId(baseDn), genId);
      assertEquals(replServer2.getGenerationId(baseDn), genId);
      assertEquals(replServer3.getGenerationId(baseDn), genId);
      assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), genId);
      assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), genId);
      assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), genId);
      debugInfo("Adding reset task to DS.");
      taskReset = TestCaseUtils.makeEntry(
@@ -1451,7 +1242,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-reset-generation-id",
        "ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask",
        "ds-task-class-name: org.opends.server.replication.service.SetGenerationIdTask",
        "ds-task-reset-generation-id-domain-base-dn: " + baseDnStr,
        "ds-task-reset-generation-id-new-value: -1");
      addTask(taskReset, ResultCode.SUCCESS, null);
@@ -1460,9 +1251,9 @@
      debugInfo("Verifying that all replservers genIds have been reset.");
      genId = readGenIdFromSuffixRootEntry();
      assertEquals(replServer1.getGenerationId(baseDn), -1);
      assertEquals(replServer2.getGenerationId(baseDn), -1);
      assertEquals(replServer3.getGenerationId(baseDn), -1);
      assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), -1);
      assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), -1);
      assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), -1);
      debugInfo(
        "Disconnect DS from replServer1 (required in order to DEL entries).");
@@ -1579,8 +1370,8 @@
  }
  /**
   * Loop opening sessions to the Replication Server
   * to check that it handle correctly deconnection and reconnection.
   * Loop opening sessions to the Replication Server
   * to check that it handle correctly deconnection and reconnection.
   */
  @Test(enabled=false, groups="slow")
  public void testLoop() throws Exception
@@ -1604,10 +1395,10 @@
        long generationId = 1000+i;
        broker = openReplicationSession(baseDn,
            server2ID, 100, getChangelogPort(changelog1ID),
            1000, !emptyOldChanges, generationId);
            1000, !emptyOldChanges, generationId);
        debugInfo(testCase + " Expect genId to be set in memory on the replication " +
        " server side even if not wrote on disk/db since no change occurred.");
        rgenId = replServer1.getGenerationId(baseDn);
        " server side even if not wrote on disk/db since no change occurred.");
        rgenId = replServer1.getGenerationId(baseDn.toNormalizedString());
        assertEquals(rgenId, generationId);
        broker.stop();
        broker = null;
@@ -1620,9 +1411,9 @@
      postTest();
    }
  }
  /**
   * This is used to make sure that the 3 tests are run in the
   * This is used to make sure that the 3 tests are run in the
   * specified order since this is necessary.
   */
  @Test(enabled=true, groups="slow")
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/InitOnLineTest.java
@@ -60,8 +60,8 @@
import org.opends.messages.Severity;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.replication.protocol.DoneMsg;
import org.opends.server.replication.protocol.EntryMsg;
import org.opends.server.replication.protocol.ErrorMsg;
@@ -144,7 +144,7 @@
  ReplicationServer changelog2 = null;
  ReplicationServer changelog3 = null;
  boolean emptyOldChanges = true;
  ReplicationDomain replDomain = null;
  LDAPReplicationDomain replDomain = null;
  private void log(String s)
  {
@@ -181,7 +181,7 @@
    // re-enabled and this clears the backend reference and thus the underlying
    // data. So for this particular test, we use a classical backend. Let's
    // clear it.
    ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    updatedEntries = newLDIFEntries();
@@ -190,7 +190,7 @@
    connection = InternalClientConnection.getRootConnection();
    synchroServerEntry = null;
    replServerEntry = null;
    replServerEntry = null;
    taskInitFromS2 = TestCaseUtils.makeEntry(
        "dn: ds-task-id=" + UUID.randomUUID() +
@@ -198,7 +198,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-initialize-from-remote-replica",
        "ds-task-class-name: org.opends.server.tasks.InitializeTask",
        "ds-task-class-name: org.opends.server.replication.service.InitializeTask",
        "ds-task-initialize-domain-dn: " + EXAMPLE_DN,
        "ds-task-initialize-replica-server-id: " + server2ID);
@@ -208,7 +208,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-initialize-remote-replica",
        "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask",
        "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask",
        "ds-task-initialize-domain-dn: " + EXAMPLE_DN,
        "ds-task-initialize-replica-server-id: " + server2ID);
@@ -218,7 +218,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-initialize-remote-replica",
        "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask",
        "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask",
        "ds-task-initialize-domain-dn: " + EXAMPLE_DN,
        "ds-task-initialize-replica-server-id: all");
  }
@@ -321,7 +321,7 @@
          {
            break;
          }
          Thread.sleep(10);
          Thread.sleep(100);
        }
      } while (completionTime == null);
@@ -485,8 +485,10 @@
    // Send entries
    try
    {
      RoutableMsg initTargetMessage = new InitializeTargetMsg(
          baseDn, server2ID, destinationServerID, requestorID, updatedEntries.length);
      RoutableMsg initTargetMessage =
        new InitializeTargetMsg(
          EXAMPLE_DN, server2ID, destinationServerID, requestorID,
          updatedEntries.length);
      broker.publish(initTargetMessage);
      for (String entry : updatedEntries)
@@ -536,7 +538,7 @@
        {
          EntryMsg em = (EntryMsg)msg;
          log("Broker " + serverID + " receives entry " + new String(em.getEntryBytes()));
          entriesReceived++;
          entriesReceived+=countEntryLimits(em.getEntryBytes());
        }
        else if (msg instanceof DoneMsg)
        {
@@ -572,6 +574,30 @@
  }
  /**
   * Count the number of entries in the provided byte[].
   * This is based on the hypothesis that the entries are separated
   * by a "\n\n" String.
   *
   * @param   entryBytes
   * @return  The number of entries in the provided byte[].
   */
  private int countEntryLimits(byte[] entryBytes)
  {
    int entryCount = 0;
    int count = 0;
    while (count<=entryBytes.length-2)
    {
      if ((entryBytes[count] == '\n') && (entryBytes[count+1] == '\n'))
      {
        entryCount++;
        count++;
      }
      count++;
    }
    return entryCount;
  }
  /**
   * Creates a new replicationServer.
   * @param changelogId The serverID of the replicationServer to create.
   * @return The new replicationServer.
@@ -592,7 +618,7 @@
      ReplServerFakeConfiguration conf =
        new ReplServerFakeConfiguration(
            getChangelogPort(changelogId),
            "initOnlineTest" + getChangelogPort(changelogId) + testCase + "Db",
            "initOnlineTest" + getChangelogPort(changelogId) + testCase + "Db",
            0,
            changelogId,
            0,
@@ -638,7 +664,7 @@
      // Clear the backend
      ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
      LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
      synchroServerEntry = TestCaseUtils.entryFromLdifString(synchroServerLdif);
      DirectoryServer.getConfigHandler().addEntry(synchroServerEntry, null);
@@ -646,7 +672,7 @@
        "Unable to add the synchronized server");
      configEntryList.add(synchroServerEntry.getDN());
      replDomain = ReplicationDomain.retrievesReplicationDomain(baseDn);
      replDomain = LDAPReplicationDomain.retrievesReplicationDomain(baseDn);
      assertTrue(!replDomain.ieRunning(),
        "ReplicationDomain: Import/Export is not expected to be running");
@@ -699,7 +725,7 @@
        server2 = openReplicationSession(DN.decode(EXAMPLE_DN),
          server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges);
      Thread.sleep(2000);
      // Thread.sleep(2000);
      // In S1 launch the total update
      addTask(taskInitFromS2, ResultCode.SUCCESS, null);
@@ -729,7 +755,7 @@
    catch(Exception e)
    {
      fail(testCase + " Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e));
    } finally
    } finally
    {
      afterTest();
    }
@@ -744,7 +770,7 @@
    String testCase = "initializeExport";
    log("Starting "+testCase);
    try
    {
      changelog1 = createChangelogServer(changelog1ID, testCase);
@@ -758,9 +784,11 @@
        server2 = openReplicationSession(DN.decode(EXAMPLE_DN),
          server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges);
      Thread.sleep(3000);
      // Not needed anymore since OpenReplicationSession
      // checks for session establishment ?
      // Thread.sleep(3000);
      InitializeRequestMsg initMsg = new InitializeRequestMsg(baseDn,
      InitializeRequestMsg initMsg = new InitializeRequestMsg(EXAMPLE_DN,
        server2ID, server1ID);
      server2.publish(initMsg);
@@ -782,7 +810,7 @@
    String testCase = "initializeTargetExport";
    log("Starting " + testCase);
    try
    {
@@ -799,7 +827,7 @@
        server2 = openReplicationSession(DN.decode(EXAMPLE_DN),
          server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges);
      Thread.sleep(1000);
      // Thread.sleep(1000);
      // Launch in S1 the task that will initialize S2
      addTask(taskInitTargetS2, ResultCode.SUCCESS, null);
@@ -846,7 +874,7 @@
        server3 = openReplicationSession(DN.decode(EXAMPLE_DN),
          server3ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges);
      Thread.sleep(1000);
      // Thread.sleep(1000);
      // Launch in S1 the task that will initialize S2
      addTask(taskInitTargetAll, ResultCode.SUCCESS, null);
@@ -891,7 +919,14 @@
      // S2 publishes entries to S1
      makeBrokerPublishEntries(server2, server2ID, server1ID, server2ID);
      Thread.sleep(10000); // FIXME - how to know the import is done
      // wait until the replication domain has expected generationID
      // this should indicate that the import occured correctly.
      for (int count = 0; count < 100; count++)
      {
        if (replDomain.getGenerationID() == 56869)
          break;
        Thread.sleep(200);
      }
      // Test that entries have been imported in S1
      testEntriesInDb();
@@ -926,7 +961,7 @@
          "objectclass: top",
          "objectclass: ds-task",
          "objectclass: ds-task-initialize-remote-replica",
          "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask",
          "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask",
          "ds-task-initialize-domain-dn: foo",
          "ds-task-initialize-remote-replica-server-id: " + server2ID);
      addTask(taskInitTarget, ResultCode.INVALID_DN_SYNTAX,
@@ -939,7 +974,7 @@
          "objectclass: top",
          "objectclass: ds-task",
          "objectclass: ds-task-initialize-remote-replica",
          "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask",
          "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask",
          "ds-task-initialize-domain-dn: dc=foo",
          "ds-task-initialize-remote-replica-server-id: " + server2ID);
      addTask(taskInitTarget, ResultCode.OTHER,
@@ -987,11 +1022,11 @@
          "objectclass: top",
          "objectclass: ds-task",
          "objectclass: ds-task-initialize-from-remote-replica",
          "ds-task-class-name: org.opends.server.tasks.InitializeTask",
          "ds-task-class-name: org.opends.server.replication.service.InitializeTask",
          "ds-task-initialize-domain-dn: foo",
          "ds-task-initialize-replica-server-id: " + server2ID);
      addTask(taskInit, ResultCode.INVALID_DN_SYNTAX,
          ERR_TASK_INITIALIZE_INVALID_DN.get());
          ERR_NO_MATCHING_DOMAIN.get());
      // Domain base dn not related to any domain
      taskInit = TestCaseUtils.makeEntry(
@@ -1000,10 +1035,11 @@
          "objectclass: top",
          "objectclass: ds-task",
          "objectclass: ds-task-initialize-from-remote-replica",
          "ds-task-class-name: org.opends.server.tasks.InitializeTask",
          "ds-task-class-name: org.opends.server.replication.service.InitializeTask",
          "ds-task-initialize-domain-dn: dc=foo",
          "ds-task-initialize-replica-server-id: " + server2ID);
      addTask(taskInit, ResultCode.OTHER, ERR_NO_MATCHING_DOMAIN.get());
      addTask(taskInit, ResultCode.INVALID_DN_SYNTAX,
          ERR_NO_MATCHING_DOMAIN.get());
      // Invalid Source
      taskInit = TestCaseUtils.makeEntry(
@@ -1012,7 +1048,7 @@
          "objectclass: top",
          "objectclass: ds-task",
          "objectclass: ds-task-initialize-from-remote-replica",
          "ds-task-class-name: org.opends.server.tasks.InitializeTask",
          "ds-task-class-name: org.opends.server.replication.service.InitializeTask",
          "ds-task-initialize-domain-dn: " + baseDn,
          "ds-task-initialize-replica-server-id: -3");
      addTask(taskInit, ResultCode.OTHER,
@@ -1083,25 +1119,29 @@
      // Check that the list of connected LDAP servers is correct
      // in each replication servers
      List<String> l1 = changelog1.getReplicationServerDomain(baseDn, false).
      List<String> l1 = changelog1.getReplicationServerDomain(
          baseDn.toNormalizedString(), false).
        getConnectedLDAPservers();
      assertEquals(l1.size(), 1);
      assertEquals(l1.get(0), String.valueOf(server1ID));
      List<String> l2;
    l2 = changelog2.getReplicationServerDomain(baseDn, false).getConnectedLDAPservers();
    l2 = changelog2.getReplicationServerDomain(
        baseDn.toNormalizedString(), false).getConnectedLDAPservers();
      assertEquals(l2.size(), 2);
      assertTrue(l2.contains(String.valueOf(server2ID)));
      assertTrue(l2.contains(String.valueOf(server3ID)));
      List<String> l3;
    l3 = changelog3.getReplicationServerDomain(baseDn, false).getConnectedLDAPservers();
    l3 = changelog3.getReplicationServerDomain(
        baseDn.toNormalizedString(), false).getConnectedLDAPservers();
      assertEquals(l3.size(), 0);
      // Test updates
      broker3.stop();
      Thread.sleep(1000);
    l2 = changelog2.getReplicationServerDomain(baseDn, false).getConnectedLDAPservers();
    l2 = changelog2.getReplicationServerDomain(
        baseDn.toNormalizedString(), false).getConnectedLDAPservers();
      assertEquals(l2.size(), 1);
      assertEquals(l2.get(0), String.valueOf(server2ID));
@@ -1109,7 +1149,8 @@
        server3ID, 100, getChangelogPort(changelog2ID), 1000, emptyOldChanges);
      broker2.stop();
      Thread.sleep(1000);
    l2 = changelog2.getReplicationServerDomain(baseDn, false).getConnectedLDAPservers();
    l2 = changelog2.getReplicationServerDomain(
        baseDn.toNormalizedString(), false).getConnectedLDAPservers();
      assertEquals(l2.size(), 1);
      assertEquals(l2.get(0), String.valueOf(server3ID));
@@ -1124,7 +1165,7 @@
      afterTest();
    }
  }
  @Test(enabled=true, groups="slow")
  public void initializeTargetExportMultiSS() throws Exception
  {
@@ -1153,7 +1194,7 @@
            server2ID, 100, getChangelogPort(changelog2ID), 1000, emptyOldChanges);
      }
      Thread.sleep(1000);
     // Thread.sleep(1000);
      // Launch in S1 the task that will initialize S2
      addTask(taskInitTargetS2, ResultCode.SUCCESS, null);
@@ -1200,7 +1241,8 @@
        log(testCase + " Will connect server 2 to " + changelog2ID);
        server2 = openReplicationSession(DN.decode(EXAMPLE_DN),
          server2ID, 100, getChangelogPort(changelog2ID),
          1000, emptyOldChanges, changelog1.getGenerationId(baseDn));
          1000, emptyOldChanges,
          changelog1.getGenerationId(baseDn.toNormalizedString()));
      }
      // Connect a broker acting as server 3 to Repl Server 3
@@ -1212,15 +1254,16 @@
        log(testCase + " Will connect server 3 to " + changelog3ID);
        server3 = openReplicationSession(DN.decode(EXAMPLE_DN),
          server3ID, 100, getChangelogPort(changelog3ID),
          1000, emptyOldChanges, changelog1.getGenerationId(baseDn));
          1000, emptyOldChanges,
          changelog1.getGenerationId(baseDn.toNormalizedString()));
      }
      Thread.sleep(500);
      // Thread.sleep(500);
      // S3 sends init request
      log(testCase + " server 3 Will send reqinit to " + server1ID);
      InitializeRequestMsg initMsg =
        new InitializeRequestMsg(baseDn, server3ID, server1ID);
        new InitializeRequestMsg(EXAMPLE_DN, server3ID, server1ID);
      server3.publish(initMsg);
      // S3 should receive target, entries & done
@@ -1268,7 +1311,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-initialize-from-remote-replica",
        "ds-task-class-name: org.opends.server.tasks.InitializeTask",
        "ds-task-class-name: org.opends.server.replication.service.InitializeTask",
        "ds-task-initialize-domain-dn: " + baseDn,
        "ds-task-initialize-replica-server-id: " + 20);
@@ -1284,7 +1327,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-initialize-from-remote-replica",
        "ds-task-class-name: org.opends.server.tasks.InitializeTask",
        "ds-task-class-name: org.opends.server.replication.service.InitializeTask",
        "ds-task-initialize-domain-dn: " + baseDn,
        "ds-task-initialize-replica-server-id: " + server1ID);
@@ -1326,7 +1369,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-initialize-remote-replica",
        "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask",
        "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask",
        "ds-task-initialize-domain-dn: " + baseDn,
        "ds-task-initialize-replica-server-id: " + 0);
@@ -1400,7 +1443,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-initialize-from-remote-replica",
        "ds-task-class-name: org.opends.server.tasks.InitializeTask",
        "ds-task-class-name: org.opends.server.replication.service.InitializeTask",
        "ds-task-initialize-domain-dn: " + baseDn,
        "ds-task-initialize-replica-server-id: " + server2ID);
@@ -1414,7 +1457,7 @@
        "objectclass: top",
        "objectclass: ds-task",
        "objectclass: ds-task-initialize-from-remote-replica",
        "ds-task-class-name: org.opends.server.tasks.InitializeTask",
        "ds-task-class-name: org.opends.server.replication.service.InitializeTask",
        "ds-task-initialize-domain-dn: " + baseDn,
        "ds-task-initialize-replica-server-id: " + server2ID);
@@ -1537,8 +1580,8 @@
    super.classCleanUp();
    // Clear the backend
    ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    paranoiaCheck();
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ProtocolWindowTest.java
@@ -31,6 +31,8 @@
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import org.opends.server.replication.service.ReplicationBroker;
import java.net.ServerSocket;
import java.net.SocketTimeoutException;
import java.util.Iterator;
@@ -49,7 +51,6 @@
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.ReplicationMsg;
import org.opends.server.replication.server.ReplServerFakeConfiguration;
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReSyncTest.java
@@ -39,14 +39,13 @@
import org.opends.server.core.AddOperationBasis;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.opends.server.TestCaseUtils.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
@@ -97,7 +96,7 @@
    // data. So for this particular test, we use a classical backend. Let's
    // clear it and create the root entry
    ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    addEntry("dn: dc=example,dc=com\n" + "objectClass: top\n"
        + "objectClass: domain\n");
@@ -108,7 +107,7 @@
        + "objectClass: ds-cfg-replication-server\n"
        + "cn: Replication Server\n"
        + "ds-cfg-replication-port:" + replServerPort + "\n"
        + "ds-cfg-replication-db-directory: ReSyncTest\n"
        + "ds-cfg-replication-db-directory: ReSyncTest\n"
        + "ds-cfg-replication-server-id: 104\n";
    replServerEntry = TestCaseUtils.entryFromLdifString(replServerLdif);
@@ -153,7 +152,7 @@
       entry.getUserAttributes(), entry.getOperationalAttributes());
    addOp.setInternalOperation(true);
    addOp.run();
    entryList.add(entry.getDN());
    return addOp.getResultCode();
  }
@@ -258,7 +257,7 @@
   if (getEntry(DN.decode("dc=fooUniqueName2," + EXAMPLE_DN), 30000, true) == null)
     fail("The Directory has not been resynchronized after the restore.");
  }
  /**
   * Clean up the environment.
   *
@@ -272,7 +271,7 @@
    super.classCleanUp();
    // Clear the backend
    ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    paranoiaCheck();
  }
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
@@ -58,10 +58,10 @@
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.plugin.PersistentServerState;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.replication.protocol.ErrorMsg;
import org.opends.server.replication.protocol.ReplSessionSecurity;
import org.opends.server.replication.protocol.ReplicationMsg;
@@ -104,7 +104,7 @@
  // TestCaseUtils.initializeTestBackend(true).
  // (using the default TestCaseUtils.TEST_ROOT_DN_STRING suffix)
  protected static final long TEST_DN_WITH_ROOT_ENTRY_GENID = 5095L;
  /**
   * Generation id for a fully empty domain.
   */
@@ -139,8 +139,8 @@
  // Call the paranoiaCheck at test cleanup or not.
  // Must not been touched except if sub class has its own clean up code,
  // for instance:
  // @AfterClass
  // for instance:
  // @AfterClass
  // public void classCleanUp() throws Exception
  // {
  //   callParanoiaCheck = false;
@@ -202,8 +202,8 @@
    long genId = TEST_DN_WITH_ROOT_ENTRY_GENID;
    try
    {
      ReplicationDomain replDomain = ReplicationDomain.retrievesReplicationDomain(baseDn);
      genId = replDomain.getGenerationId();
      LDAPReplicationDomain replDomain = LDAPReplicationDomain.retrievesReplicationDomain(baseDn);
      genId = replDomain.getGenerationID();
    }
    catch(Exception e) {}
    return genId;
@@ -233,15 +233,14 @@
        long generationId)
  throws Exception, SocketException
  {
    ServerState state;
    ServerState state = new ServerState();
    if (emptyOldChanges)
       state = new PersistentServerState(baseDn, serverId);
    else
       state = new ServerState();
       new PersistentServerState(baseDn, serverId, new ServerState());
    ReplicationBroker broker = new ReplicationBroker(null,
        state, baseDn, serverId, 0, 0, 0, 0,
        window_size, 0, generationId, getReplSessionSecurity(), (byte)1);
        state, baseDn.toNormalizedString(), serverId, window_size,
        generationId, 100000, getReplSessionSecurity(), (byte)1);
    ArrayList<String> servers = new ArrayList<String>(1);
    servers.add("localhost:" + port);
    broker.start(servers);
@@ -347,8 +346,8 @@
          throws Exception, SocketException
  {
    ReplicationBroker broker = new ReplicationBroker(null,
        state, baseDn, serverId, 0, 0, 0, 0, window_size, 0, generationId,
        getReplSessionSecurity(), (byte)1);
        state, baseDn.toNormalizedString(), serverId, window_size, generationId,
        100000, getReplSessionSecurity(), (byte)1);
    ArrayList<String> servers = new ArrayList<String>(1);
    servers.add("localhost:" + port);
    broker.start(servers);
@@ -380,16 +379,14 @@
        boolean emptyOldChanges, long generationId)
            throws Exception, SocketException
  {
    ServerState state;
    ServerState state = new ServerState();
    if (emptyOldChanges)
       state = new PersistentServerState(baseDn, serverId);
    else
       state = new ServerState();
       new PersistentServerState(baseDn, serverId, new ServerState());
    ReplicationBroker broker = new ReplicationBroker(null,
        state, baseDn, serverId, maxRcvQueue, 0,
        maxSendQueue, 0, window_size, 0, generationId,
        getReplSessionSecurity(), (byte)1);
        state, baseDn.toNormalizedString(), serverId, window_size,
        generationId, 0, getReplSessionSecurity(), (byte)1);
    ArrayList<String> servers = new ArrayList<String>(1);
    servers.add("localhost:" + port);
    broker.start(servers);
@@ -652,7 +649,7 @@
  protected long getMonitorAttrValue(DN baseDn, String attr) throws Exception
  {
    String monitorFilter =
         "(&(cn=replication plugin*)(base-dn=" + baseDn + "))";
         "(&(cn=replication Domain*)(base-dn=" + baseDn + "))";
    InternalSearchOperation op;
    int count = 0;
@@ -1111,5 +1108,5 @@
    {
      fail("addEntries Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e));
    }
  }
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
@@ -46,9 +46,8 @@
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyOperationBasis;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.replication.protocol.ModifyMsg;
import org.opends.server.replication.protocol.ReplicationMsg;
import org.opends.server.types.Attribute;
@@ -100,7 +99,7 @@
        + "objectClass: ds-cfg-replication-server\n"
        + "cn: Replication Server\n"
        + "ds-cfg-replication-port: " + replServerPort + "\n"
        + "ds-cfg-replication-db-directory: SchemaReplicationTest\n"
        + "ds-cfg-replication-db-directory: SchemaReplicationTest\n"
        + "ds-cfg-replication-server-id: 105\n";
    replServerEntry = TestCaseUtils.entryFromLdifString(replServerLdif);
@@ -247,9 +246,6 @@
   * Checks that changes done to the schema files are pushed to the
   * ReplicationServers and that the ServerState is updated in the schema
   * file.
   * FIXME: This test is disabled because it has side effects.
   * It causes schema tests in org.opends.server.core.AddOperationTestCase
   * to fail when running the build test target.
   */
  @Test(enabled=true, dependsOnMethods = { "replaySchemaChange" })
  public void pushSchemaFilesChange() throws Exception
@@ -261,7 +257,7 @@
    ReplicationBroker broker =
      openReplicationSession(baseDn, (short) 3, 100, replServerPort, 5000, true);
    try
    {
      // create a schema change Notification
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/StressTest.java
@@ -47,7 +47,7 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.ReplicationMsg;
import org.opends.server.types.Attribute;
@@ -203,7 +203,7 @@
        + "objectClass: ds-cfg-replication-server\n"
        + "cn: Replication Server\n"
        + "ds-cfg-replication-port: " + replServerPort + "\n"
        + "ds-cfg-replication-db-directory: StressTest\n"
        + "ds-cfg-replication-db-directory: StressTest\n"
        + "ds-cfg-replication-server-id: 106\n";
    replServerEntry = TestCaseUtils.entryFromLdifString(replServerLdif);
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
@@ -58,8 +58,8 @@
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.replication.protocol.HeartbeatThread;
@@ -160,7 +160,7 @@
        + "objectClass: ds-cfg-replication-server\n"
        + "cn: Replication Server\n"
        + "ds-cfg-replication-port: " + replServerPort + "\n"
        + "ds-cfg-replication-db-directory: UpdateOperationTest\n"
        + "ds-cfg-replication-db-directory: UpdateOperationTest\n"
        + "ds-cfg-replication-server-id: 107\n";
    replServerEntry = TestCaseUtils.entryFromLdifString(replServerLdif);
@@ -676,32 +676,32 @@
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    /*
     * Test that modify conflict resolution is able to detect that
     * Test that modify conflict resolution is able to detect that
     * because there is a conflict between a MODIFYDN and a MODIFY,
     * when a MODIFY is replayed the attribute that is being modified is
     * now the RDN of the entry and therefore should not be deleted.
     */
    // send a modify operation attempting to replace the RDN entry
    // send a modify operation attempting to replace the RDN entry
    // with a new value
    mods = generatemods("uid", "AnotherUid");
    modMsg = new ModifyMsg(gen.newChangeNumber(),
        personWithUUIDEntry.getDN(), mods,
        user1entryUUID);
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(modMsg);
    // check that the modify has been applied.
    found = checkEntryHasAttribute(personWithUUIDEntry.getDN(),
                           "uid", "AnotherUid", 10000, true);
    if (found == false)
      fail("The modification has not been correctly replayed.");
    assertEquals(getMonitorDelta(), 1);
    /*
     * Test that the conflict resolution code is able to detect
     * that an entry has been renamed and that a new entry has
@@ -1105,19 +1105,19 @@
    assertNotNull(resultEntry,
        "The ADD replication message was NOT applied under ou=baseDn2,"+baseDn);
    assertEquals(getMonitorDelta(), 1);
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    //
    // Check that when a delete is conflicting with Add of some entries
    // below the deleted entries, the child entry that have been added
    // before the deleted is replayed gets renamed correctly.
    //
    // add domain1 entry with 2 children : domain2 and domain3
    addEntry(domain1);
    domain1uid = getEntryUUID(DN.decode(domain1dn));
@@ -1129,10 +1129,10 @@
        "entryUUID = " + domain2uid + "+dc=domain2,ou=people," + TEST_ROOT_DN_STRING);
    DN conflictDomain3dn = DN.decode(
        "entryUUID = " + domain3uid + "+dc=domain3,ou=people," + TEST_ROOT_DN_STRING);
    updateMonitorCount(baseDn, unresolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    // delete domain1
    delMsg = new DeleteMsg(domain1dn, gen.newChangeNumber(), domain1uid);
    broker.publish(delMsg);
@@ -1140,7 +1140,7 @@
    // check that the domain1 has correctly been deleted
    assertNull(getEntry(DN.decode(domain1dn), 10000, false),
        "The DELETE replication message was not replayed");
    // check that domain2 and domain3 have been renamed
    assertNotNull(getEntry(conflictDomain2dn, 1000, true),
        "The conflicting entries were not created");
@@ -1149,23 +1149,23 @@
    // check that the 2 conflicting entries have been correctly marked
    assertTrue(checkEntryHasAttribute(conflictDomain2dn,
        ReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true));
        LDAPReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true));
    assertTrue(checkEntryHasAttribute(conflictDomain3dn,
        ReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true));
        LDAPReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true));
    // check that unresolved conflict count has been incremented
    assertEquals(getMonitorDelta(), 1);
    // Check that an administrative alert was generated
    // because the conflict has not been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount+2,
        "An alert was incorrectly generated when resolving conflicts");
    // delete the resulting entries for the next test
    delEntry(conflictDomain2dn);
    delEntry(conflictDomain3dn);
    //
    // Check that when an entry is added on one master below an entry
    // that is currently deleted on another master, the replay of the
@@ -1176,18 +1176,18 @@
        domain2.getObjectClassAttribute(),
        domain2.getAttributes(), new ArrayList<Attribute>());
    broker.publish(addMsg);
    // check that conflict entry was created
    assertNotNull(getEntry(conflictDomain2dn, 1000, true),
      "The conflicting entries were not created");
    // check that the entry have been correctly marked as conflicting.
    assertTrue(checkEntryHasAttribute(conflictDomain2dn,
        ReplicationDomain.DS_SYNC_CONFLICT, domain2dn, 1000, true));
        LDAPReplicationDomain.DS_SYNC_CONFLICT, domain2dn, 1000, true));
    // check that unresolved conflict count has been incremented
    assertEquals(getMonitorDelta(), 1);
    // Check that when an entry is deleted on a first master and
    // renamed on a second master and the rename is replayed last
    // this is correctly detected as a resolved conflict.
@@ -1215,14 +1215,14 @@
    // if the monitor counter did not get incremented after 200sec
    // then something got wrong.
    assertTrue(count < 200);
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    /*
     * Check that a conflict is detected when an entry is
     * Check that a conflict is detected when an entry is
     * moved below an entry that does not exist.
     */
    updateMonitorCount(baseDn, unresolvedMonitorAttr);
@@ -1234,7 +1234,7 @@
        "uid=wrong, ou=people," + TEST_ROOT_DN_STRING,
        "uid=newrdn");
    broker.publish(modDnMsg);
    count = 0;
    while ((count<2000) && getMonitorDelta() == 0)
    {
@@ -1246,11 +1246,11 @@
    // if the monitor counter did not get incremented after 200sec
    // then something got wrong.
    assertTrue(count < 200);
    // check that the entry have been correctly marked as conflicting.
    assertTrue(checkEntryHasAttribute(
        DN.decode("uid=new person,ou=baseDn2,"+baseDn),
        ReplicationDomain.DS_SYNC_CONFLICT,
        LDAPReplicationDomain.DS_SYNC_CONFLICT,
        "uid=newrdn,ou=baseDn2,ou=People," + TEST_ROOT_DN_STRING, 1000, true));
    broker.stop();
@@ -1426,7 +1426,7 @@
      if (found == false)
        fail("The modification has not been correctly replayed.");
      // Test that replication is able to add attribute that do
      // not exist in the schema.
      List<Modification> invalidMods = generatemods("badattribute", "value");
@@ -1569,7 +1569,7 @@
        "Starting replication test : infiniteReplayLoop"));
    final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING);
    Thread.sleep(2000);
    ReplicationBroker broker =
      openReplicationSession(baseDn, (short) 11, 100, replServerPort, 1000, true);
@@ -1709,7 +1709,7 @@
        "Starting synchronization test : CNGeneratorAdjust"));
    final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING);
    /*
     * Open a session to the replicationServer using the broker API.
     * This must use a different serverId to that of the directory server.
@@ -1731,7 +1731,7 @@
        user3UUID,
        baseUUID,
        user3Entry.getObjectClassAttribute(),
        user3Entry.getAttributes(),
        user3Entry.getAttributes(),
        new ArrayList<Attribute>());
    broker.publish(addMsg);
@@ -1745,8 +1745,8 @@
    List<Modification> mods = generatemods("telephonenumber", "01 02 45");
    ModifyOperationBasis modOp = new ModifyOperationBasis(
        connection,
        InternalClientConnection.nextOperationID(),
        InternalClientConnection.nextMessageID(),
        InternalClientConnection.nextOperationID(),
        InternalClientConnection.nextMessageID(),
        null,
        user3Entry.getDN(),
        mods);
@@ -1758,10 +1758,10 @@
    assertTrue(msg instanceof ModifyMsg,
      "The received replication message is not a MODIFY msg");
    ModifyMsg modMsg = (ModifyMsg) msg;
    assertEquals(addMsg.getChangeNumber().getTimeSec(),
    assertEquals(addMsg.getChangeNumber().getTimeSec(),
                 modMsg.getChangeNumber().getTimeSec(),
                "The MOD timestamp should have been adjusted to the ADD one");
    // Delete the entries to clean the database.
    DeleteMsg delMsg =
      new DeleteMsg(
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
New file
@@ -0,0 +1,1535 @@
/*
 * 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-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import org.opends.server.types.ResultCode;
import org.opends.messages.Category;
import org.opends.messages.Message;
import org.opends.messages.Severity;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.core.DeleteOperationBasis;
import org.opends.server.core.DirectoryServer;
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.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.ReplSessionSecurity;
import org.opends.server.replication.protocol.ServerStartMsg;
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.loggers.debug.DebugTracer;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.replication.protocol.AckMsg;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteStringFactory;
import org.testng.annotations.BeforeClass;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchScope;
import org.testng.annotations.Test;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.opends.server.TestCaseUtils.*;
import static org.testng.Assert.fail;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
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;
/**
 * Test the client part (plugin) of the assured feature in both safe data and
 * safe read modes. We use a fake RS and control the behaviour of the client
 * DS (timeout, wait for acks, error handling...)
 * Also check for monitoring values for assured replication
 */
public class AssuredReplicationPluginTest
  extends ReplicationTestCase
{
  /**
   * The port of the replicationServer.
   */
  private int replServerPort;
  private byte RS_SERVER_ID = (byte) 90;
  // Sleep time of the RS, before sending an ack
  private static final long NO_TIMEOUT_RS_SLEEP_TIME = 2000;
  private String testName = this.getClass().getSimpleName();
  // Create to distincts base dn, one for safe data replication, the other one
  // for safe read replication
  private String SAFE_DATA_DN = "ou=safe-data," + TEST_ROOT_DN_STRING;
  private String SAFE_READ_DN = "ou=safe-read," + TEST_ROOT_DN_STRING;
  private String NOT_ASSURED_DN = "ou=not-assured," + TEST_ROOT_DN_STRING;
  private Entry safeDataDomainCfgEntry = null;
  private Entry safeReadDomainCfgEntry = null;
  private Entry notAssuredDomainCfgEntry = null;
  // The fake replication server
  private FakeReplicationServer replicationServer = null;
  // Definitions for the scenario the RS supports
  private static final int NOT_ASSURED_SCENARIO = 1;
  private static final int TIMEOUT_SCENARIO = 2;
  private static final int NO_TIMEOUT_SCENARIO = 3;
  private static final int SAFE_READ_MANY_ERRORS = 4;
  private static final int SAFE_DATA_MANY_ERRORS = 5;
  // The tracer object for the debug logger
  private static final DebugTracer TRACER = getTracer();
  private void debugInfo(String s)
  {
    logError(Message.raw(Category.SYNC, Severity.NOTICE, s));
    if (debugEnabled())
    {
      TRACER.debugInfo("** TEST **" + s);
    }
  }
  /**
   * Before starting the tests configure some stuff
   */
  @BeforeClass
  @Override
  public void setUp() throws Exception
  {
    super.setUp();
    // Find  a free port for the replicationServer
    ServerSocket socket = TestCaseUtils.bindFreePort();
    replServerPort = socket.getLocalPort();
    socket.close();
    // Create base dns for each tested modes
    String topEntry = "dn: " + SAFE_DATA_DN + "\n" + "objectClass: top\n" +
      "objectClass: organizationalUnit\n";
    addEntry(TestCaseUtils.entryFromLdifString(topEntry));
    topEntry = "dn: " + SAFE_READ_DN + "\n" + "objectClass: top\n" +
      "objectClass: organizationalUnit\n";
    addEntry(TestCaseUtils.entryFromLdifString(topEntry));
    topEntry = "dn: " + NOT_ASSURED_DN + "\n" + "objectClass: top\n" +
      "objectClass: organizationalUnit\n";
    addEntry(TestCaseUtils.entryFromLdifString(topEntry));
  }
  /**
   * 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();
    assertNotNull(getEntry(entry.getDN(), 1000, true));
  }
  /**
   * Creates a domain using the passed assured settings.
   * Returns the matching config entry added to the config backend.
   */
  private Entry createAssuredDomain(AssuredMode assuredMode, int safeDataLevel,
    long assuredTimeout) throws Exception
  {
    String baseDn = null;
    switch (assuredMode)
    {
      case SAFE_READ_MODE:
        baseDn = SAFE_READ_DN;
        break;
      case SAFE_DATA_MODE:
        baseDn = SAFE_DATA_DN;
        break;
      default:
        fail("Unexpected assured level.");
    }
    // Create an assured config entry ldif, matching passed assured settings
    String prefixLdif = "dn: cn=" + testName + ", cn=domains, " +
      SYNCHRO_PLUGIN_DN + "\n" + "objectClass: top\n" +
      "objectClass: ds-cfg-replication-domain\n" + "cn: " + testName + "\n" +
      "ds-cfg-base-dn: " + baseDn + "\n" +
      "ds-cfg-replication-server: localhost:" + replServerPort + "\n" +
      "ds-cfg-server-id: 1\n" + "ds-cfg-receive-status: true\n" +
      // heartbeat = 10 min so no need to emulate heartbeat in fake RS: session
      // not closed by client
      "ds-cfg-heartbeat-interval: 600000ms\n";
    String configEntryLdif = null;
    switch (assuredMode)
    {
      case SAFE_READ_MODE:
        configEntryLdif = prefixLdif + "ds-cfg-assured-type: safe-read\n" +
          "ds-cfg-assured-timeout: " + assuredTimeout + "ms\n";
        break;
      case SAFE_DATA_MODE:
        configEntryLdif = prefixLdif + "ds-cfg-assured-type: safe-data\n" +
          "ds-cfg-assured-sd-level: " + safeDataLevel + "\n" +
          "ds-cfg-assured-timeout: " + assuredTimeout + "ms\n";
        break;
      default:
        fail("Unexpected assured level.");
    }
    Entry domainCfgEntry = TestCaseUtils.entryFromLdifString(configEntryLdif);
    // Add the config entry to create the replicated domain
    DirectoryServer.getConfigHandler().addEntry(domainCfgEntry, null);
    assertNotNull(DirectoryServer.getConfigEntry(domainCfgEntry.getDN()),
      "Unable to add the domain config entry: " + configEntryLdif);
    return domainCfgEntry;
  }
  /**
   * Creates a domain without assured mode
   * Returns the matching config entry added to the config backend.
   */
  private Entry createNotAssuredDomain() throws Exception
  {
    // Create a not assured config entry ldif
    String configEntryLdif = "dn: cn=" + testName + ", cn=domains, " +
      SYNCHRO_PLUGIN_DN + "\n" + "objectClass: top\n" +
      "objectClass: ds-cfg-replication-domain\n" + "cn: " + testName + "\n" +
      "ds-cfg-base-dn: " + NOT_ASSURED_DN + "\n" +
      "ds-cfg-replication-server: localhost:" + replServerPort + "\n" +
      "ds-cfg-server-id: 1\n" + "ds-cfg-receive-status: true\n" +
      // heartbeat = 10 min so no need to emulate heartbeat in fake RS: session
      // not closed by client
      "ds-cfg-heartbeat-interval: 600000ms\n";
    Entry domainCfgEntry = TestCaseUtils.entryFromLdifString(configEntryLdif);
    // Add the config entry to create the replicated domain
    DirectoryServer.getConfigHandler().addEntry(domainCfgEntry, null);
    assertNotNull(DirectoryServer.getConfigEntry(domainCfgEntry.getDN()),
      "Unable to add the domain config entry: " + configEntryLdif);
    return domainCfgEntry;
  }
  /**
   * Removes a domain deleting the passed config entry
   */
  private void removeDomain(Entry domainCfgEntry)
  {
    DeleteOperationBasis op;
    // Delete entries
    try
    {
      DN dn = domainCfgEntry.getDN();
      logError(Message.raw(Category.SYNC, Severity.NOTICE,
        "cleaning config entry " + dn));
      op = new DeleteOperationBasis(connection, InternalClientConnection.
        nextOperationID(), InternalClientConnection.nextMessageID(), null,
        dn);
      op.run();
      if ((op.getResultCode() != ResultCode.SUCCESS) &&
        (op.getResultCode() != ResultCode.NO_SUCH_OBJECT))
      {
        fail("Deleting config entry" + dn +
          " failed: " + op.getResultCode().getResultCodeName());
      }
    } catch (NoSuchElementException e)
    {
      // done
    }
  }
  /**
   * The fake replication server used to emulate RS behaviour the way we want
   * for assured features test
   */
  private class FakeReplicationServer extends Thread
  {
    private ServerSocket listenSocket;
    private boolean shutdown = false;
    private ProtocolSession session = null;
    // Parameters given at constructor time
    private int port;
    private short serverId = -1;
    boolean isAssured = false; // Default value for config
    AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE; // Default value for config
    byte safeDataLevel = (byte) 1; // Default value for config
    // Predefined config parameters
    private int degradedStatusThreshold = 5000;
    // Parameters set with received server start message
    private String baseDn = null;
    private long generationId = -1L;
    private ServerState serverState = null;
    private int windowSize = -1;
    private byte groupId = (byte) -1;
    private boolean sslEncryption = false;
    // The scenario this RS is expecting
    private int scenario = -1;
    // parameters at handshake are ok
    private boolean handshakeOk = false;
    // signal that the current scenario the RS must execute reached the point
    // where the main code can perform test assertion
    private boolean scenarioExecuted = false;
    // Constructor for RS receiving updates in SR assured mode or not assured
    // The assured boolean means:
    // - true: SR mode
    // - false: not assured
    public FakeReplicationServer(int port, short serverId, boolean assured)
    {
      this.port = port;
      this.serverId = serverId;
      if (assured)
      {
        this.isAssured = true;
        this.assuredMode = AssuredMode.SAFE_READ_MODE;
      }
    }
    // Constructor for RS receiving updates in SD assured mode
    public FakeReplicationServer(int port, short serverId, int safeDataLevel)
    {
      this.port = port;
      this.serverId = serverId;
      this.isAssured = true;
      this.assuredMode = AssuredMode.SAFE_DATA_MODE;
      this.safeDataLevel = (byte) safeDataLevel;
    }
    /**
     * Starts the fake RS, expecting and testing the passed scenario.
     */
    public void start(int scenario)
    {
      // Store expected test case
      this.scenario = scenario;
      // Start listening
      start();
    }
    /**
     * Wait for DS connections
     */
    @Override
    public void run()
    {
      // Create server socket
      try
      {
        listenSocket = new ServerSocket();
        listenSocket.bind(new InetSocketAddress(port));
      } catch (IOException e)
      {
        fail("Fake replication server could not bind to port:" + port);
      }
      Socket newSocket = null;
      // Loop waiting for DS connections
      while (!shutdown)
      {
        try
        {
          newSocket = listenSocket.accept();
          newSocket.setTcpNoDelay(true);
          newSocket.setKeepAlive(true);
          // Create client session
          ReplSessionSecurity replSessionSecurity = new ReplSessionSecurity();
          session = replSessionSecurity.createServerSession(newSocket,
            ReplSessionSecurity.HANDSHAKE_TIMEOUT);
          if (session == null) // Error, go back to accept
          {
            continue;
          }
          // Ok, now call connection handling code + special code for the
          // configured test
          handleClientConnection();
        } catch (Exception e)
        {
          // The socket has probably been closed as part of the
          // shutdown
        }
      }
    }
    /**
     * Shutdown the Replication Server service and all its connections.
     */
    public void shutdown()
    {
      if (shutdown)
      {
        return;
      }
      shutdown = true;
      // Shutdown the listener thread
      try
      {
        if (listenSocket != null)
        {
          listenSocket.close();
        }
      } catch (IOException e)
      {
        // replication Server service is closing anyway.
      }
      /*
       * Shutdown any current client handling code
       */
      try
      {
        if (session != null)
        {
          session.close();
        }
      } catch (IOException e)
      {
        // ignore.
      }
      try
      {
        join();
      } catch (InterruptedException ie)
      {
      }
    }
    /**
     * Handle the handshake processing with the connecting DS
     * returns true if handshake was performed without errors
     */
    private boolean performHandshake()
    {
      try
      {
        // Receive server start
        ServerStartMsg serverStartMsg = (ServerStartMsg) session.receive();
        baseDn = serverStartMsg.getBaseDn();
        serverState = serverStartMsg.getServerState();
        generationId = serverStartMsg.getGenerationId();
        windowSize = serverStartMsg.getWindowSize();
        groupId = serverStartMsg.getGroupId();
        sslEncryption = serverStartMsg.getSSLEncryption();
        // Send replication server start
        String serverURL = ("localhost:" + port);
        ReplServerStartMsg replServerStartMsg = new ReplServerStartMsg(serverId,
          serverURL, baseDn, windowSize, serverState,
          ProtocolVersion.getCurrentVersion(), generationId, sslEncryption,
          groupId, degradedStatusThreshold);
        session.publish(replServerStartMsg);
        if (!sslEncryption)
        {
          session.stopEncryption();
        }
        // Read start session
        StartSessionMsg startSessionMsg = (StartSessionMsg) session.receive();
        // Sanity checking for assured parameters
        boolean receivedIsAssured = startSessionMsg.isAssured();
        assertEquals(receivedIsAssured, isAssured);
        AssuredMode receivedAssuredMode = startSessionMsg.getAssuredMode();
        assertEquals(receivedAssuredMode, assuredMode);
        byte receivedSafeDataLevel = startSessionMsg.getSafeDataLevel();
        assertEquals(receivedSafeDataLevel, safeDataLevel);
        ServerStatus receivedServerStatus = startSessionMsg.getStatus();
        assertEquals(receivedServerStatus, ServerStatus.NORMAL_STATUS);
        List<String> receivedReferralsURLs = startSessionMsg.getReferralsURLs();
        assertEquals(receivedReferralsURLs.size(), 0);
        debugInfo("Received start session assured parameters are ok.");
        // Send topo view
        List<RSInfo> rsList = new ArrayList<RSInfo>();
        RSInfo rsInfo = new RSInfo(serverId, generationId, groupId);
        rsList.add(rsInfo);
        TopologyMsg topologyMsg = new TopologyMsg(new ArrayList<DSInfo>(),
          rsList);
        session.publish(topologyMsg);
      } catch (IOException e)
      {
        // Probably un-connection of DS looking for best server
        return false;
      } catch (Exception e)
      {
        fail("Unexpected exception in fake replication server handshake " +
          "processing: " + e);
        return false;
      }
      return true;
    }
    /**
     * Tells if the received handshake parameters regarding assured config were
     * ok and handshake phase is terminated.
     */
    public boolean isHandshakeOk()
    {
      return handshakeOk;
    }
    /**
     * Tells the main code that the fake RS executed enough of the expected
     * scenario and can perform test assertion
     */
    public boolean isScenarioExecuted()
    {
      return scenarioExecuted;
    }
    /**
     * Handle client connection then call code specific to configured test
     */
    private void handleClientConnection()
    {
      // Handle DS connexion
      if (!performHandshake())
      {
        try
        {
          session.close();
        } catch (IOException e)
        {
          // nothing to do
        }
        return;
      }
      // If we come here, assured parameters sent by DS are as expected and
      // handshake phase is terminated
      handshakeOk = true;
      // Now execute the requested scenario
      switch (scenario)
      {
        case NOT_ASSURED_SCENARIO:
          executeNotAssuredScenario();
          break;
        case TIMEOUT_SCENARIO:
          executeTimeoutScenario();
          break;
        case NO_TIMEOUT_SCENARIO:
          executeNoTimeoutScenario();
          break;
        case SAFE_READ_MANY_ERRORS:
          executeSafeReadManyErrorsScenario();
          break;
        case SAFE_DATA_MANY_ERRORS:
          executeSafeDataManyErrorsScenario();
          break;
        default:
          fail("Unknown scenario: " + scenario);
      }
    }
    /**
     * Read the coming update and check parameters are not assured
     */
    private void executeNotAssuredScenario()
    {
      try
      {
        UpdateMsg updateMsg = (UpdateMsg) session.receive();
        checkUpdateAssuredParameters(updateMsg);
        scenarioExecuted = true;
      } catch (Exception e)
      {
        fail("Unexpected exception in fake replication server executeNotAssuredScenario " +
          "processing: " + e);
      }
    }
    /**
     * Read the coming update and make the client time out by not sending back
     * the ack
     */
    private void executeTimeoutScenario()
    {
      try
      {
        UpdateMsg updateMsg = (UpdateMsg) session.receive();
        checkUpdateAssuredParameters(updateMsg);
        scenarioExecuted = true;
        // We do not send back an ack and the client code is expected to be
        // blocked at least for the programmed timeout time.
      } catch (Exception e)
      {
        fail("Unexpected exception in fake replication server executeTimeoutScenario " +
          "processing: " + e);
      }
    }
    /**
     * Read the coming update, sleep some time then send back an ack
     */
    private void executeNoTimeoutScenario()
    {
      try
      {
        UpdateMsg updateMsg = (UpdateMsg) session.receive();
        checkUpdateAssuredParameters(updateMsg);
        // Sleep before sending back the ack
        sleep(NO_TIMEOUT_RS_SLEEP_TIME);
        // Send the ack without errors
        AckMsg ackMsg = new AckMsg(updateMsg.getChangeNumber());
        session.publish(ackMsg);
        scenarioExecuted = true;
      } catch (Exception e)
      {
        fail("Unexpected exception in fake replication server executeNoTimeoutScenario " +
          "processing: " + e);
      }
    }
    /**
     * Check that received update assured parameters are as defined at RS start
     */
    private void checkUpdateAssuredParameters(UpdateMsg updateMsg)
    {
      assertEquals(updateMsg.isAssured(), isAssured);
      assertEquals(updateMsg.getAssuredMode(), assuredMode);
      assertEquals(updateMsg.getSafeDataLevel(), safeDataLevel);
      debugInfo("Received update assured parameters are ok.");
    }
    /**
     * Read the coming seaf read mode updates and send back acks with errors
     */
    private void executeSafeReadManyErrorsScenario()
    {
      try
      {
        // Read first update
        UpdateMsg updateMsg = (UpdateMsg) session.receive();
        checkUpdateAssuredParameters(updateMsg);
        // Sleep before sending back the ack
        sleep(NO_TIMEOUT_RS_SLEEP_TIME);
        // Send an ack with errors:
        // - replay error
        // - server 10 error, server 20 error
        List<Short> serversInError = new ArrayList<Short>();
        serversInError.add((short)10);
        serversInError.add((short)20);
        AckMsg ackMsg = new AckMsg(updateMsg.getChangeNumber(), false, false, true, serversInError);
        session.publish(ackMsg);
        // Read second update
        updateMsg = (UpdateMsg) session.receive();
        checkUpdateAssuredParameters(updateMsg);
        // Sleep before sending back the ack
        sleep(NO_TIMEOUT_RS_SLEEP_TIME);
        // Send an ack with errors:
        // - timeout error
        // - wrong status error
        // - replay error
        // - server 10 error, server 20 error, server 30 error
        serversInError = new ArrayList<Short>();
        serversInError.add((short)10);
        serversInError.add((short)20);
        serversInError.add((short)30);
        ackMsg = new AckMsg(updateMsg.getChangeNumber(), true, true, true, serversInError);
        session.publish(ackMsg);
        // Read third update
        updateMsg = (UpdateMsg) session.receive();
        checkUpdateAssuredParameters(updateMsg);
        // let timeout occur
        scenarioExecuted = true;
      } catch (Exception e)
      {
        fail("Unexpected exception in fake replication server executeSafeReadManyErrorsScenario " +
          "processing: " + e);
      }
    }
    /**
     * Read the coming seaf data mode updates and send back acks with errors
     */
    private void executeSafeDataManyErrorsScenario()
    {
      try
      {
        // Read first update
        UpdateMsg updateMsg = (UpdateMsg) session.receive();
        checkUpdateAssuredParameters(updateMsg);
        // Sleep before sending back the ack
        sleep(NO_TIMEOUT_RS_SLEEP_TIME);
        // Send an ack with errors:
        // - timeout error
        // - server 10 error
        List<Short> serversInError = new ArrayList<Short>();
        serversInError.add((short)10);
        AckMsg ackMsg = new AckMsg(updateMsg.getChangeNumber(), true, false, false, serversInError);
        session.publish(ackMsg);
        // Read second update
        updateMsg = (UpdateMsg) session.receive();
        checkUpdateAssuredParameters(updateMsg);
        // Sleep before sending back the ack
        sleep(NO_TIMEOUT_RS_SLEEP_TIME);
        // Send an ack with errors:
        // - timeout error
        // - server 10 error, server 20 error
        serversInError = new ArrayList<Short>();
        serversInError.add((short)10);
        serversInError.add((short)20);
        ackMsg = new AckMsg(updateMsg.getChangeNumber(), true, false, false, serversInError);
        session.publish(ackMsg);
        // Read third update
        updateMsg = (UpdateMsg) session.receive();
        checkUpdateAssuredParameters(updateMsg);
        // let timeout occur
        scenarioExecuted = true;
      } catch (Exception e)
      {
        fail("Unexpected exception in fake replication server executeSafeDataManyErrorsScenario " +
          "processing: " + e);
      }
    }
  }
  /**
   * Sleep a while
   */
  private void sleep(long time)
  {
    try
    {
      Thread.sleep(time);
    } catch (InterruptedException ex)
    {
      fail("Error sleeping " + ex);
    }
  }
  /**
   * Tests that a DS performing a modification in safe data mode waits for
   * the ack of the RS for the configured timeout time, then times out.
   */
  @Test
  public void testSafeDataModeTimeout() throws Exception
  {
    int TIMEOUT = 5000;
    String testcase = "testSafeDataModeTimeout";
    try
    {
      // Create and start a RS expecting clients in safe data assured mode with
      // safe data level 2
      replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID,
        1);
      replicationServer.start(TIMEOUT_SCENARIO);
      // Create a safe data assured domain
      safeDataDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_DATA_MODE, 1,
        TIMEOUT);
      // Wait for connection of domain to RS
      waitForConnectionToRs(testcase, replicationServer);
      // Make an LDAP update (add an entry)
      long startTime = System.currentTimeMillis(); // Time the update has been initiated
      String entry = "dn: ou=assured-sd-timeout-entry," + SAFE_DATA_DN + "\n" +
        "objectClass: top\n" +
        "objectClass: organizationalUnit\n";
      addEntry(TestCaseUtils.entryFromLdifString(entry));
      // In this scenario, the fake RS will not send back an ack so we expect
      // the add entry code (LDAP client code emulation) to be blocked for the
      // timeout value at least. If the time we have slept is lower, timeout
      // handling code is not working...
      long endTime = System.currentTimeMillis();
      assertTrue((endTime - startTime) >= TIMEOUT);
      assertTrue(replicationServer.isScenarioExecuted());
      // Check monitoring values
      DN baseDn = DN.decode(SAFE_DATA_DN);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0);
      Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      assertTrue(errorsByServer.isEmpty());
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 1);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      //  errors by server list for sd mode should be [[rsId:1]]
      assertEquals(errorsByServer.size(), 1);
      Integer nError = errorsByServer.get((short)RS_SERVER_ID);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
    } finally
    {
      endTest();
    }
  }
  /**
   * Tests that a DS performing a modification in safe read mode waits for
   * the ack of the RS for the configured timeout time, then times out.
   */
  @Test
  public void testSafeReadModeTimeout() throws Exception
  {
    int TIMEOUT = 5000;
    String testcase = "testSafeReadModeTimeout";
    try
    {
      // Create and start a RS expecting clients in safe read assured mode
      replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID,
        true);
      replicationServer.start(TIMEOUT_SCENARIO);
      // Create a safe read assured domain
      safeReadDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_READ_MODE, 0,
        TIMEOUT);
      // Wait for connection of domain to RS
      waitForConnectionToRs(testcase, replicationServer);
      // Make an LDAP update (add an entry)
      long startTime = System.currentTimeMillis(); // Time the update has been initiated
      String entry = "dn: ou=assured-sr-timeout-entry," + SAFE_READ_DN + "\n" +
        "objectClass: top\n" +
        "objectClass: organizationalUnit\n";
      addEntry(TestCaseUtils.entryFromLdifString(entry));
      // In this scenario, the fake RS will not send back an ack so we expect
      // the add entry code (LDAP client code emulation) to be blocked for the
      // timeout value at least. If the time we have slept is lower, timeout
      // handling code is not working...
      long endTime = System.currentTimeMillis();
      assertTrue((endTime - startTime) >= TIMEOUT);
      assertTrue(replicationServer.isScenarioExecuted());
      // Check monitoring values
      DN baseDn = DN.decode(SAFE_READ_DN);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0);
      Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      //  errors by server list for sr mode should be [[rsId:1]]
      assertEquals(errorsByServer.size(), 1);
      Integer nError = errorsByServer.get((short)RS_SERVER_ID);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      assertTrue(errorsByServer.isEmpty());
    } finally
    {
      endTest();
    }
  }
  /**
   * Tests parameters sent in session handshake an updates, when not using
   * assured replication
   */
  @Test
  public void testNotAssuredSession() throws Exception
  {
    String testcase = "testNotAssuredSession";
    try
    {
      // Create and start a RS expecting not assured clients
      replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID,
        false);
      replicationServer.start(NOT_ASSURED_SCENARIO);
      // Create a not assured domain
      notAssuredDomainCfgEntry = createNotAssuredDomain();
      // Wait for connection of domain to RS
      waitForConnectionToRs(testcase, replicationServer);
      // Make an LDAP update (add an entry)
      String entry = "dn: ou=not-assured-entry," + NOT_ASSURED_DN + "\n" +
        "objectClass: top\n" +
        "objectClass: organizationalUnit\n";
      addEntry(TestCaseUtils.entryFromLdifString(entry));
      // Wait for entry received by RS
      waitForScenarioExecutedOnRs(testcase, replicationServer);
      // No more test to do here
    } finally
    {
      endTest();
    }
  }
  /**
   * Wait for connection to the fake replication server or times out with error
   * after some seconds
   */
  private void waitForConnectionToRs(String testCase, FakeReplicationServer rs)
  {
    int nsec = -1;
    do
    {
      nsec++;
      if (nsec == 10) // 10 seconds timeout
        fail(testCase + ": timeout waiting for domain connection to fake RS after " + nsec + " seconds.");
      sleep(1000);
    } while (!rs.isHandshakeOk());
  }
  /**
   * Wait for the scenario to be executed by the fake replication server or
   * times out with error after some seconds
   */
  private void waitForScenarioExecutedOnRs(String testCase, FakeReplicationServer rs)
  {
    int nsec = -1;
    do
    {
      nsec++;
      if (nsec == 10) // 10 seconds timeout
        fail(testCase + ": timeout waiting for scenario to be exectued on fake RS after " + nsec + " seconds.");
      sleep(1000);
    } while (!rs.isScenarioExecuted());
  }
  private void endTest()
  {
    if (replicationServer != null)
    {
      replicationServer.shutdown();
    }
    if (safeDataDomainCfgEntry != null)
    {
      removeDomain(safeDataDomainCfgEntry);
    }
    if (safeReadDomainCfgEntry != null)
    {
      removeDomain(safeReadDomainCfgEntry);
    }
    if (notAssuredDomainCfgEntry != null)
    {
      removeDomain(notAssuredDomainCfgEntry);
    }
  }
  /**
   * Tests that a DS performing a modification in safe data mode receives the RS
   * ack and does not return before returning it.
   */
  @Test
  public void testSafeDataModeAck() throws Exception
  {
    int TIMEOUT = 5000;
    String testcase = "testSafeDataModeAck";
    try
    {
      // Create and start a RS expecting clients in safe data assured mode with
      // safe data level 2
      replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID,
        2);
      replicationServer.start(NO_TIMEOUT_SCENARIO);
      // Create a safe data assured domain
      safeDataDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_DATA_MODE, 2,
        TIMEOUT);
      // Wait for connection of domain to RS
      waitForConnectionToRs(testcase, replicationServer);
      // Make an LDAP update (add an entry)
      long startTime = System.currentTimeMillis(); // Time the update has been initiated
      String entry = "dn: ou=assured-sd-no-timeout-entry," + SAFE_DATA_DN + "\n" +
        "objectClass: top\n" +
        "objectClass: organizationalUnit\n";
      addEntry(TestCaseUtils.entryFromLdifString(entry));
      // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME
      // seconds, so we expect the add entry code (LDAP client code emulation) to be blocked
      // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value.
      long endTime = System.currentTimeMillis();
      long callTime = endTime - startTime;
      assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT));
      assertTrue(replicationServer.isScenarioExecuted());
      // Check monitoring values
      DN baseDn = DN.decode(SAFE_DATA_DN);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0);
      Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      assertTrue(errorsByServer.isEmpty());
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      assertTrue(errorsByServer.isEmpty());
    } finally
    {
      endTest();
    }
  }
  /**
   * Tests that a DS performing a modification in safe read mode receives the RS
   * ack and does not return before returning it.
   */
  @Test
  public void testSafeReadModeAck() throws Exception
  {
    int TIMEOUT = 5000;
    String testcase = "testSafeReadModeAck";
    try
    {
      // Create and start a RS expecting clients in safe read assured mode
      replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID,
        true);
      replicationServer.start(NO_TIMEOUT_SCENARIO);
      // Create a safe read assured domain
      safeReadDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_READ_MODE, 0,
        TIMEOUT);
      // Wait for connection of domain to RS
      waitForConnectionToRs(testcase, replicationServer);
      // Make an LDAP update (add an entry)
      long startTime = System.currentTimeMillis(); // Time the update has been initiated
      String entry = "dn: ou=assured-sr-no-timeout-entry," + SAFE_READ_DN + "\n" +
        "objectClass: top\n" +
        "objectClass: organizationalUnit\n";
      addEntry(TestCaseUtils.entryFromLdifString(entry));
      // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME
      // seconds, so we expect the add entry code (LDAP client code emulation) to be blocked
      // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value.
      long endTime = System.currentTimeMillis();
      long callTime = endTime - startTime;
      assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT));
      assertTrue(replicationServer.isScenarioExecuted());
      // Check monitoring values
      DN baseDn = DN.decode(SAFE_READ_DN);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0);
      Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      assertTrue(errorsByServer.isEmpty());
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      assertTrue(errorsByServer.isEmpty());
    } finally
    {
      endTest();
    }
  }
  /**
   * DS performs many successive modifications in safe data mode and receives RS
   * acks with various errors. Check for monitoring right errors
   */
  @Test
  public void testSafeDataManyErrors() throws Exception
  {
    int TIMEOUT = 5000;
    String testcase = "testSafeDataManyErrors";
    try
    {
      // Create and start a RS expecting clients in safe data assured mode with
      // safe data level 3
      replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID,
        3);
      replicationServer.start(SAFE_DATA_MANY_ERRORS);
      // Create a safe data assured domain
      safeDataDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_DATA_MODE, 3,
        TIMEOUT);
      // Wait for connection of domain to RS
      waitForConnectionToRs(testcase, replicationServer);
      // Make a first LDAP update (add an entry)
      long startTime = System.currentTimeMillis(); // Time the update has been initiated
      String entryDn = "ou=assured-sd-many-errors-entry," + SAFE_DATA_DN;
      String entry = "dn: " + entryDn + "\n" +
        "objectClass: top\n" +
        "objectClass: organizationalUnit\n";
      addEntry(TestCaseUtils.entryFromLdifString(entry));
      // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME
      // seconds, so we expect the add entry code (LDAP client code emulation) to be blocked
      // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value.
      long endTime = System.currentTimeMillis();
      long callTime = endTime - startTime;
      assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT));
      // Check monitoring values
      // The expected ack for the first update is:
      // - timeout error
      // - server 10 error
      DN baseDn = DN.decode(SAFE_DATA_DN);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0);
      Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      assertTrue(errorsByServer.isEmpty());
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 1);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      //  errors by server list for sd mode should be [[10:1]]
      assertEquals(errorsByServer.size(), 1);
      Integer nError = errorsByServer.get((short)10);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
      // Make a second LDAP update (delete the entry)
      startTime = System.currentTimeMillis(); // Time the update has been initiated
      deleteEntry(entryDn);
      // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME
      // seconds, so we expect the delete entry code (LDAP client code emulation) to be blocked
      // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value.
      endTime = System.currentTimeMillis();
      callTime = endTime - startTime;
      assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT));
      // Check monitoring values
      // The expected ack for the second update is:
      // - timeout error
      // - server 10 error, server 20 error
      baseDn = DN.decode(SAFE_DATA_DN);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      assertTrue(errorsByServer.isEmpty());
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 2);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 2);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      //  errors by server list for sd mode should be [[10:2],[20:1]]
      assertEquals(errorsByServer.size(), 2);
      nError = errorsByServer.get((short)10);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 2);
      nError = errorsByServer.get((short)20);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
      // Make a third LDAP update (re-add the entry)
      startTime = System.currentTimeMillis(); // Time the update has been initiated
      addEntry(TestCaseUtils.entryFromLdifString(entry));
      // In this scenario, the fake RS will not send back an ack so we expect
      // the add entry code (LDAP client code emulation) to be blocked for the
      // timeout value at least. If the time we have slept is lower, timeout
      // handling code is not working...
      endTime = System.currentTimeMillis();
      assertTrue((endTime - startTime) >= TIMEOUT);
      assertTrue(replicationServer.isScenarioExecuted());
      // Check monitoring values
      // No ack should have comen back, so timeout incremented (flag and error for rs)
      baseDn = DN.decode(SAFE_DATA_DN);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      assertTrue(errorsByServer.isEmpty());
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 3);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 3);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      //  errors by server list for sd mode should be [[10:2],[20:1],[rsId:1]]
      assertEquals(errorsByServer.size(), 3);
      nError = errorsByServer.get((short)10);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 2);
      nError = errorsByServer.get((short)20);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
      nError = errorsByServer.get((short)RS_SERVER_ID);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
    } finally
    {
      endTest();
    }
  }
  /**
   * DS performs many successive modifications in safe read mode and receives RS
   * acks with various errors. Check for monitoring right errors
   */
  @Test
  public void testSafeReadManyErrors() throws Exception
  {
    int TIMEOUT = 5000;
    String testcase = "testSafeReadManyErrors";
    try
    {
      // Create and start a RS expecting clients in safe read assured mode
      replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID,
        true);
      replicationServer.start(SAFE_READ_MANY_ERRORS);
      // Create a safe read assured domain
      safeReadDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_READ_MODE, 0,
        TIMEOUT);
      // Wait for connection of domain to RS
      waitForConnectionToRs(testcase, replicationServer);
      // Make a first LDAP update (add an entry)
      long startTime = System.currentTimeMillis(); // Time the update has been initiated
      String entryDn = "ou=assured-sr-many-errors-entry," + SAFE_READ_DN;
      String entry = "dn: " + entryDn + "\n" +
        "objectClass: top\n" +
        "objectClass: organizationalUnit\n";
      addEntry(TestCaseUtils.entryFromLdifString(entry));
      // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME
      // seconds, so we expect the add entry code (LDAP client code emulation) to be blocked
      // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value.
      long endTime = System.currentTimeMillis();
      long callTime = endTime - startTime;
      assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT));
      // Check monitoring values
      // The expected ack for the first update is:
      // - replay error
      // - server 10 error, server 20 error
      DN baseDn = DN.decode(SAFE_READ_DN);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 1);
      Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      //  errors by server list for sr mode should be [[10:1],[20:1]]
      assertEquals(errorsByServer.size(), 2);
      Integer nError = errorsByServer.get((short)10);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
      nError = errorsByServer.get((short)20);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      assertTrue(errorsByServer.isEmpty());
      // Make a second LDAP update (delete the entry)
      startTime = System.currentTimeMillis(); // Time the update has been initiated
      deleteEntry(entryDn);
      // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME
      // seconds, so we expect the delete entry code (LDAP client code emulation) to be blocked
      // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value.
      endTime = System.currentTimeMillis();
      callTime = endTime - startTime;
      assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT));
      // Check monitoring values
      // The expected ack for the second update is:
      // - timeout error
      // - wrong status error
      // - replay error
      // - server 10 error, server 20 error, server 30 error
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 2);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 2);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 2);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      //  errors by server list for sr mode should be [[10:2],[20:2],[30:1]]
      assertEquals(errorsByServer.size(), 3);
      nError = errorsByServer.get((short)10);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 2);
      nError = errorsByServer.get((short)20);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 2);
      nError = errorsByServer.get((short)30);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      assertTrue(errorsByServer.isEmpty());
      // Make a third LDAP update (re-add the entry)
      startTime = System.currentTimeMillis(); // Time the update has been initiated
      addEntry(TestCaseUtils.entryFromLdifString(entry));
      // In this scenario, the fake RS will not send back an ack so we expect
      // the add entry code (LDAP client code emulation) to be blocked for the
      // timeout value at least. If the time we have slept is lower, timeout
      // handling code is not working...
      endTime = System.currentTimeMillis();
      assertTrue((endTime - startTime) >= TIMEOUT);
      assertTrue(replicationServer.isScenarioExecuted());
      // Check monitoring values
      // No ack should have comen back, so timeout incremented (flag and error for rs)
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 3);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 3);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 2);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 2);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE);
      //  errors by server list for sr mode should be [[10:2],[20:2],[30:1],[rsId:1]]
      assertEquals(errorsByServer.size(), 4);
      nError = errorsByServer.get((short)10);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 2);
      nError = errorsByServer.get((short)20);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 2);
      nError = errorsByServer.get((short)30);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
      nError = errorsByServer.get((short)RS_SERVER_ID);
      assertNotNull(nError);
      assertEquals(nError.intValue(), 1);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0);
      assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0);
      errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE);
      assertTrue(errorsByServer.isEmpty());
    } finally
    {
      endTest();
    }
  }
  /**
   * Delete an entry from the database
   */
  private void deleteEntry(String dn) throws Exception
  {
    DN realDn = DN.decode(dn);
    DeleteOperationBasis delOp = new DeleteOperationBasis(connection,
      InternalClientConnection.nextOperationID(), InternalClientConnection.
      nextMessageID(), null, realDn);
    delOp.setInternalOperation(true);
    delOp.run();
    assertNull(DirectoryServer.getEntry(realDn));
  }
  /**
   * Get the errors by servers from cn=monitor, according to the requested base dn
   * and the requested mode
   * This corresponds to the values for multi valued attributes:
   * - assured-sr-server-not-acknowledged-updates in SR mode
   * - assured-sd-server-timeout-updates in SD mode
   */
  protected Map<Short,Integer> getErrorsByServers(DN baseDn,
    AssuredMode assuredMode) throws Exception
  {
    /*
     * Find monitoring entry for requested base DN
     */
    String monitorFilter =
         "(&(cn=replication Domain*)(base-dn=" + baseDn + "))";
    InternalSearchOperation op;
    int count = 0;
    do
    {
      if (count++>0)
        Thread.sleep(100);
      op = connection.processSearch(
                                    ByteStringFactory.create("cn=monitor"),
                                    SearchScope.SINGLE_LEVEL,
                                    LDAPFilter.decode(monitorFilter));
    }
    while (op.getSearchEntries().isEmpty() && (count<100));
    if (op.getSearchEntries().isEmpty())
      throw new Exception("Could not read monitoring information");
    SearchResultEntry entry = op.getSearchEntries().getFirst();
    if (entry == null)
      throw new Exception("Could not find monitoring entry");
    /*
     * Find the multi valued attribute matching the requested assured mode
     */
    String assuredAttr = null;
    switch(assuredMode)
    {
      case SAFE_READ_MODE:
        assuredAttr = "assured-sr-server-not-acknowledged-updates";
        break;
      case SAFE_DATA_MODE:
        assuredAttr = "assured-sd-server-timeout-updates";
        break;
      default:
        throw new Exception("Unknown assured type");
    }
    List<Attribute> attrs = entry.getAttribute(assuredAttr);
    Map<Short,Integer> resultMap = new HashMap<Short,Integer>();
    if ( (attrs == null) || (attrs.isEmpty()) )
      return resultMap; // Empty map
    Attribute attr = attrs.get(0);
    Iterator<AttributeValue> attValIt = attr.iterator();
    // Parse and store values
    while (attValIt.hasNext())
    {
      String srvStr = attValIt.next().getStringValue();
      StringTokenizer strtok = new StringTokenizer(srvStr, ":");
      String token = strtok.nextToken();
      if (token != null)
      {
        Short serverId = Short.valueOf(token);
        token = strtok.nextToken();
        if (token != null)
        {
          Integer nerrors = Integer.valueOf(token);
          resultMap.put(serverId, nerrors);
        }
      }
    }
    return resultMap;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ComputeBestServerTest.java
@@ -27,7 +27,7 @@
package org.opends.server.replication.plugin;
import java.util.HashMap;
import static org.opends.server.replication.plugin.ReplicationBroker.*;
import static org.opends.server.replication.service.ReplicationBroker.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
@@ -40,8 +40,6 @@
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.plugin.ReplicationBroker.ServerInfo;
import org.opends.server.types.DN;
import org.testng.annotations.Test;
/**
@@ -85,7 +83,7 @@
    final String WINNER = "winner";
    // Create my state
    ServerState mySt = new ServerState();
    ServerState mySt = new ServerState();
    ChangeNumber cn = new ChangeNumber(2L, 0, myId2); // Should not be used inside algo
    mySt.update(cn);
    cn = new ChangeNumber(3L, 0, myId3); // Should not be used inside algo
@@ -103,7 +101,7 @@
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
@@ -148,7 +146,7 @@
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
@@ -170,7 +168,7 @@
    short myId1 = 1;
    short myId2 = 2;
    short myId3 = 3;
    // definitions for server names
    final String WINNER = "winner";
@@ -195,7 +193,7 @@
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
@@ -242,7 +240,7 @@
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
@@ -300,7 +298,7 @@
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
@@ -360,11 +358,11 @@
    rsInfos.put(LOOSER1, new ServerInfo(aState, (byte)2));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
  /**
   * Test with 2 replication servers, none of them from our group id.
   *
@@ -418,7 +416,7 @@
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)2));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
@@ -487,11 +485,11 @@
    rsInfos.put(LOOSER2, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
  /**
   * Test with 3 replication servers, up to date, but 2 different group ids.
   *
@@ -558,7 +556,7 @@
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
@@ -605,7 +603,7 @@
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
@@ -663,11 +661,11 @@
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
  /**
   * Test with 3 replication servers, late.
   *
@@ -732,11 +730,11 @@
    rsInfos.put(LOOSER2, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
  /**
   * Test with 6 replication servers, some up, some late, one null
   *
@@ -752,8 +750,8 @@
    // definitions for server ids
    short myId1 = 1;
    short myId2 = 2;
    short myId3 = 3;
    short myId3 = 3;
    // definitions for server names
    final String WINNER = "winner";
    final String LOOSER1 = "looser1";
@@ -803,7 +801,7 @@
    cn = new ChangeNumber(10L, 0, myId3);
    aState.update(cn);
    rsInfos.put(LOOSER3, new ServerInfo(aState, (byte)1));
    // State for server 4
    aState = new ServerState();
    cn = new ChangeNumber(6L, 0, myId1);
@@ -813,7 +811,7 @@
    cn = new ChangeNumber(8L, 0, myId3);
    aState.update(cn);
    rsInfos.put(WINNER, new ServerInfo(aState, (byte)1));
    // State for server 5 (null one for our serverid)
    aState = new ServerState();
    cn = new ChangeNumber(5L, 0, myId2);
@@ -821,7 +819,7 @@
    cn = new ChangeNumber(5L, 0, myId3);
    aState.update(cn);
    rsInfos.put(LOOSER4, new ServerInfo(aState, (byte)1));
    // State for server 6
    aState = new ServerState();
    cn = new ChangeNumber(5L, 0, myId1);
@@ -833,7 +831,7 @@
    rsInfos.put(LOOSER5, new ServerInfo(aState, (byte)1));
    String bestServer =
      computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1);
      computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1);
    assertEquals(bestServer, WINNER, "Wrong best replication server.");
  }
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/GroupIdHandshakeTest.java
@@ -30,7 +30,7 @@
import java.net.ServerSocket;
import java.util.SortedSet;
import java.util.TreeSet;
import static org.opends.server.replication.plugin.ReplicationBroker.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
@@ -64,8 +64,8 @@
  private int rs1Port = -1;
  private int rs2Port = -1;
  private int rs3Port = -1;
  private ReplicationDomain rd1 = null;
  private ReplicationDomain rd2 = null;
  private LDAPReplicationDomain rd1 = null;
  private LDAPReplicationDomain rd2 = null;
  private ReplicationServer rs1 = null;
  private ReplicationServer rs2 = null;
  private ReplicationServer rs3 = null;
@@ -147,16 +147,6 @@
    rs3Port = -1;
  }
  private void sleep(long time)
  {
    try
    {
      Thread.sleep(time);
    } catch (InterruptedException ex)
    {
      fail("Error sleeping " + stackTraceToSingleLineString(ex));
    }
  }
  /**
   * Check connection of the provided replication domain to the provided
@@ -167,7 +157,7 @@
  {
    int rsPort = -1;
    ReplicationDomain rd = null;
    LDAPReplicationDomain rd = null;
    switch (dsId)
    {
      case DS1_ID:
@@ -281,7 +271,7 @@
  private SortedSet<String> createRSListForTestCase(String testCase)
  {
    SortedSet<String> replServers = new TreeSet<String>();
    if (testCase.equals("testRSWithSameGroupIds"))
    {
      // 2 servers used for this test case.
@@ -369,7 +359,7 @@
  /**
   * Creates a new ReplicationDomain.
   */
  private ReplicationDomain createReplicationDomain(short serverId,
  private LDAPReplicationDomain createReplicationDomain(short serverId,
    int groupId, String testCase)
  {
@@ -380,7 +370,7 @@
      DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
      DomainFakeCfg domainConf =
        new DomainFakeCfg(baseDn, serverId, replServers, groupId);
      ReplicationDomain replicationDomain =
      LDAPReplicationDomain replicationDomain =
        MultimasterReplication.createNewDomain(domainConf);
      replicationDomain.start();
      return replicationDomain;
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalCsnOrderingTest.java
@@ -158,7 +158,7 @@
  throws Exception
  {
    // Creates a rule
    HistoricalCsnOrderingMatchingRule r =
    HistoricalCsnOrderingMatchingRule r =
      new HistoricalCsnOrderingMatchingRule();
    ChangeNumber del1 = new ChangeNumber(1, (short) 0, (short) 1);
@@ -178,7 +178,7 @@
  }
  /**
   * Test that we can retrieve the entries that were missed by
   * Test that we can retrieve the entries that were missed by
   * a replication server and can  re-build operations from the historical
   * informations.
   */
@@ -227,7 +227,7 @@
    logError(Message.raw(Category.SYNC, Severity.INFORMATION,
        "First historical value:" + histValue));
    // Perform a 2nd modification to update the hist attribute with
    // Perform a 2nd modification to update the hist attribute with
    // a second value
    resultCode = TestCaseUtils.applyModifications(false,
        "dn: cn=test1," + baseDn.toString(),
@@ -256,7 +256,8 @@
    // Retrieves the entries that have changed since the first modification
    InternalSearchOperation op =
      ReplicationBroker.searchForChangedEntries(baseDn, fromChangeNumber, null);
      LDAPReplicationDomain.searchForChangedEntries(
          baseDn, fromChangeNumber, null);
    // The expected result is one entry .. the one previously modified
    assertEquals(op.getResultCode(), ResultCode.SUCCESS);
@@ -279,6 +280,6 @@
        updatesCnt++;
      }
    }
    assertTrue(updatesCnt == 3);
    assertTrue(updatesCnt == 3);
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalTest.java
@@ -27,11 +27,13 @@
package org.opends.server.replication.plugin;
import org.opends.server.replication.protocol.LDAPUpdateMsg;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.ModifyMsg;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.TestCaseUtils;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.tools.LDAPModify;
@@ -335,7 +337,7 @@
    ModifyMsg modMsg = new ModifyMsg(changeNum, dn, mods, entryuuid);
    broker.publish(modMsg);
  }
  /**
   * Test that historical information is correctly added when performaing ADD,
   * MOD and MODDN operations.
@@ -416,7 +418,7 @@
    for (FakeOperation fake : ops)
    {
      UpdateMsg msg = (UpdateMsg) fake.generateMessage();
      LDAPUpdateMsg msg = (LDAPUpdateMsg) fake.generateMessage();
      AbstractOperation op =
        msg.createOperation(InternalClientConnection.getRootConnection());
      op.setInternalOperation(true);
@@ -449,7 +451,7 @@
        AddMsg addmsg = addOp.generateMessage();
        assertTrue(dn1.equals(DN.decode(addmsg.getDn())));
        assertTrue(addmsg.getUniqueId().equals(Historical.getEntryUuid(entry)));
        String parentId = ReplicationDomain.findEntryId(dn1.getParent());
        String parentId = LDAPReplicationDomain.findEntryId(dn1.getParent());
        assertTrue(addmsg.getParentUid().equals(parentId));
        addmsg.createOperation(InternalClientConnection.getRootConnection());
      } else
@@ -462,7 +464,7 @@
        }
      }
    }
      assertEquals(count, assertCount);
    }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/IsolationTest.java
@@ -26,15 +26,10 @@
 */
package org.opends.server.replication.plugin;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import static org.testng.Assert.*;
@@ -65,7 +60,7 @@
  @Test()
  public void noUpdateIsolationPolicyTest() throws Exception
  {
    ReplicationDomain domain = null;
    LDAPReplicationDomain domain = null;
    DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    SynchronizationProvider replicationPlugin = null;
    short serverId = 1;
@@ -106,7 +101,7 @@
      op = conn.processModify(baseDn, generatemods("description", "test"));
      // check that the operation was successful.
      assertEquals(op.getResultCode(), ResultCode.SUCCESS,
      assertEquals(op.getResultCode(), ResultCode.SUCCESS,
          op.getAdditionalLogMessage().toString());
    }
    finally
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java
@@ -26,6 +26,8 @@
 */
package org.opends.server.replication.plugin;
import org.opends.server.replication.protocol.LDAPUpdateMsg;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
@@ -49,7 +51,6 @@
import org.opends.server.replication.plugin.Historical;
import org.opends.server.replication.protocol.ModifyContext;
import org.opends.server.replication.protocol.ReplicationMsg;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
@@ -1137,9 +1138,9 @@
        assertTrue(new FakeOperationComparator().compare(fk, fk) == 0);
        assertTrue(new FakeOperationComparator().compare(null , fk) < 0);
        ReplicationMsg generatedMsg = fk.generateMessage() ;
        if (generatedMsg instanceof UpdateMsg)
        if (generatedMsg instanceof LDAPUpdateMsg)
        {
          UpdateMsg new_name = (UpdateMsg) generatedMsg;
          LDAPUpdateMsg new_name = (LDAPUpdateMsg) generatedMsg;
          assertEquals(new_name.getUniqueId(),uuid);
        }
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentServerStateTest.java
@@ -28,6 +28,8 @@
import static org.testng.Assert.assertEquals;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
@@ -69,9 +71,13 @@
     * 2 ChangeNumbers have been saved in this new PersistentServerState.
     */
    DN baseDn = DN.decode(dn);
    PersistentServerState state = new PersistentServerState(baseDn, (short) 1);
    ChangeNumberGenerator gen1 = new ChangeNumberGenerator((short) 1, state);
    ChangeNumberGenerator gen2 = new ChangeNumberGenerator((short) 2, state);
    ServerState origState = new ServerState();
    PersistentServerState state =
      new PersistentServerState(baseDn, (short) 1, origState);
    ChangeNumberGenerator gen1 =
      new ChangeNumberGenerator((short) 1, origState);
    ChangeNumberGenerator gen2 =
      new ChangeNumberGenerator((short) 2, origState);
    ChangeNumber cn1 = gen1.newChangeNumber();
    ChangeNumber cn2 = gen2.newChangeNumber();
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ReplicationServerFailoverTest.java
@@ -63,8 +63,8 @@
  private static final short RS2_ID = 32;
  private int rs1Port = -1;
  private int rs2Port = -1;
  private ReplicationDomain rd1 = null;
  private ReplicationDomain rd2 = null;
  private LDAPReplicationDomain rd1 = null;
  private LDAPReplicationDomain rd2 = null;
  private ReplicationServer rs1 = null;
  private ReplicationServer rs2 = null;
@@ -298,7 +298,7 @@
  {
    int rsPort = -1;
    ReplicationDomain rd = null;
    LDAPReplicationDomain rd = null;
    switch (dsId)
    {
      case DS1_ID:
@@ -439,7 +439,7 @@
  /**
   * Creates a new ReplicationDomain.
   */
  private ReplicationDomain createReplicationDomain(DN baseDn, short serverId)
  private LDAPReplicationDomain createReplicationDomain(DN baseDn, short serverId)
  {
    SortedSet<String> replServers = new TreeSet<String>();
@@ -452,7 +452,7 @@
      DomainFakeCfg domainConf =
        new DomainFakeCfg(baseDn, serverId, replServers);
      //domainConf.setHeartbeatInterval(500);
      ReplicationDomain replicationDomain =
      LDAPReplicationDomain replicationDomain =
        MultimasterReplication.createNewDomain(domainConf);
      replicationDomain.start();
@@ -465,7 +465,7 @@
    return null;
  }
  private int findReplServerConnected(ReplicationDomain rd)
  private int findReplServerConnected(LDAPReplicationDomain rd)
  {
    int rsPort = -1;
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/StateMachineTest.java
@@ -35,7 +35,7 @@
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.opends.server.replication.plugin.ReplicationBroker.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
@@ -48,6 +48,7 @@
import org.opends.server.TestCaseUtils;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.common.DSInfo;
import org.opends.server.replication.common.ServerState;
@@ -60,7 +61,6 @@
import org.opends.server.replication.protocol.ReplicationMsg;
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.server.ReplServerFakeConfiguration;
import org.opends.server.replication.server.ReplicationServer;
import org.opends.server.types.Attribute;
@@ -71,7 +71,6 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
import static org.opends.server.TestCaseUtils.*;
/**
 * Some tests to go through the DS state machine and validate we get the
@@ -87,7 +86,7 @@
  private static final short DS3_ID = 3;
  private static final short RS1_ID = 41;
  private int rs1Port = -1;
  private ReplicationDomain ds1 = null;
  private LDAPReplicationDomain ds1 = null;
  private ReplicationBroker ds2 = null;
  private ReplicationBroker ds3 = null;
  private ReplicationServer rs1 = null;
@@ -176,7 +175,7 @@
  {
    ReplicationBroker rb = null;
    ReplicationDomain rd = null;
    LDAPReplicationDomain rd = null;
    switch (dsId)
    {
      case DS1_ID:
@@ -215,14 +214,14 @@
      // Sleep 1 second
      try
      {
        Thread.sleep(1000);
        Thread.sleep(100);
      } catch (InterruptedException ex)
      {
        fail("Error sleeping " + stackTraceToSingleLineString(ex));
      }
      nSec++;
      if (nSec > secTimeout)
      if (nSec > secTimeout*10)
      {
        // Timeout reached, end with error
        fail("checkConnection: DS " + dsId + " is not connected to the RS after "
@@ -276,7 +275,7 @@
   * Creates and starts a new ReplicationDomain configured for the replication
   * server
   */
  private ReplicationDomain createReplicationDomain(short dsId)
  private LDAPReplicationDomain createReplicationDomain(short dsId)
  {
    try
    {
@@ -286,7 +285,7 @@
      DN baseDn = DN.decode(EXAMPLE_DN);
      DomainFakeCfg domainConf =
        new DomainFakeCfg(baseDn, dsId, replServers);
      ReplicationDomain replicationDomain =
      LDAPReplicationDomain replicationDomain =
        MultimasterReplication.createNewDomain(domainConf);
      replicationDomain.start();
@@ -308,7 +307,7 @@
    throws Exception, SocketException
  {
    ReplicationBroker broker = new ReplicationBroker(null,
      state, DN.decode(EXAMPLE_DN), dsId, 0, 0, 0, 0, window, 0, generationId,
      state, EXAMPLE_DN, dsId, 100, generationId, 0,
      new ReplSessionSecurity(null, null, null, true), (byte) 1);
    ArrayList<String> servers = new ArrayList<String>(1);
    servers.add("localhost:" + rs1Port);
@@ -413,7 +412,6 @@
    try
    {
      /**
       * RS1 starts with specified threshold value
       */
@@ -440,7 +438,7 @@
      // having sent them to TCP receive queue of DS2.
      bw = new BrokerWriter(ds3, DS3_ID, false);
      bw.followAndPause(11);
      sleep(1000);
      // sleep(1000);
      /**
       * DS3 sends changes (less than threshold): DS2 should still be in normal
@@ -452,10 +450,10 @@
      {
        nChangesSent = thresholdValue - 1;
        bw.followAndPause(nChangesSent);
        sleep(7000); // Be sure status analyzer has time to test
        sleep(1000); // Be sure status analyzer has time to test
        ReplicationMsg msg = br3.getLastMsg();
        debugInfo(testCase + " Step 1: last message from writer: " + msg);
        assertTrue(msg == null);
        assertTrue(msg == null, (msg != null) ? msg.toString() : "null" );
      }
      /**
@@ -463,17 +461,29 @@
       * update topo message with status of DS2: degraded status
       */
      bw.followAndPause(thresholdValue - nChangesSent);
      sleep(7000); // Be sure status analyzer has time to test
      ReplicationMsg lastMsg = br3.getLastMsg();
      assertTrue(lastMsg != null);
      debugInfo(testCase + " Step 2: last message from writer: " + lastMsg);
      assertTrue(lastMsg instanceof TopologyMsg);
      TopologyMsg topoMsg = (TopologyMsg) lastMsg;
      List<DSInfo> dsList = topoMsg.getDsList();
      assertEquals(dsList.size(), 1);
      DSInfo ds3Info = dsList.get(0);
      assertEquals(ds3Info.getDsId(), DS2_ID);
      assertEquals(ds3Info.getStatus(), ServerStatus.DEGRADED_STATUS);
      // wait for a status MSG status analyzer to broker 3
      ReplicationMsg lastMsg = null;
      for (int count = 0; count< 50; count++)
      {
        List<DSInfo> dsList = ds3.getDsList();
        DSInfo ds3Info = null;
        if (dsList.size() > 0)
        {
          ds3Info = dsList.get(0);
        }
        if ((ds3Info != null) && (ds3Info.getDsId() == DS2_ID) &&
            (ds3Info.getStatus()== ServerStatus.DEGRADED_STATUS) )
        {
          break;
        }
        else
        {
          if (count < 50)
            sleep(200); // Be sure status analyzer has time to test
          else
            fail("DS2 did not get degraded : " + ds3Info);
        }
      }
      /**
       * DS3 sends 10 additional changes after threshold value, DS2 should still be
@@ -481,7 +491,7 @@
       */
      bw.followAndPause(10);
      bw.shutdown();
      sleep(7000); // Be sure status analyzer has time to test
      sleep(1000); // Be sure status analyzer has time to test
      lastMsg = br3.getLastMsg();
      ReplicationMsg msg = br3.getLastMsg();
      debugInfo(testCase + " Step 3: last message from writer: " + msg);
@@ -492,17 +502,28 @@
       * (create a reader to emulate replay of messages (messages read from queue))
       */
      br2 = new BrokerReader(ds2, DS2_ID);
      sleep(7000); // Be sure messages are read and status analyzer has time to test
      lastMsg = br3.getLastMsg();
      assertTrue(lastMsg != null);
      debugInfo(testCase + " Step 4: last message from writer: " + lastMsg);
      assertTrue(lastMsg instanceof TopologyMsg);
      topoMsg = (TopologyMsg) lastMsg;
      dsList = topoMsg.getDsList();
      assertEquals(dsList.size(), 1);
      ds3Info = dsList.get(0);
      assertEquals(ds3Info.getDsId(), DS2_ID);
      assertEquals(ds3Info.getStatus(), ServerStatus.NORMAL_STATUS);
      // wait for a status MSG status analyzer to broker 3
      for (int count = 0; count< 50; count++)
      {
        List<DSInfo> dsList = ds3.getDsList();
        DSInfo ds3Info = null;
        if (dsList.size() > 0)
        {
          ds3Info = dsList.get(0);
        }
        if ((ds3Info != null) && (ds3Info.getDsId() == DS2_ID) &&
            (ds3Info.getStatus()== ServerStatus.DEGRADED_STATUS) )
        {
          break;
        }
        else
        {
          if (count < 50)
            sleep(200); // Be sure status analyzer has time to test
          else
            fail("DS2 did not get degraded.");
        }
      }
    } finally
    {
@@ -617,8 +638,8 @@
      rs1.remove();
      bw.pause();
      sleepAssertStatusEquals(30, ds1, ServerStatus.NOT_CONNECTED_STATUS);
      /**
       * DS2 restarts with up to date server state (this allows to have
       * restarting RS1 not sending him some updates he already sent)
@@ -680,7 +701,7 @@
       * DS2 sends reset gen id order with bad gen id: DS1 should go in bad gen id
       * status (from degraded status this time)
       */
      resetGenId(ds2, -1); // -1 to allow next step full update and flush RS db so that DS1 can reconnect after full update
      resetGenId(ds2, -1); // -1 to allow next step full update and flush RS db so that DS1 can reconnect after full update
      sleepAssertStatusEquals(30, ds1, ServerStatus.BAD_GEN_ID_STATUS);
      bw.pause();
@@ -705,7 +726,7 @@
      ds2.stop(); // will need a new broker with another gen id restart it
      bw.shutdown();
      br.shutdown();
      long newGen = ds1.getGenerationId();
      long newGen = ds1.getGenerationID();
      curState = ds1.getServerState();
      ds2 = createReplicationBroker(DS2_ID, curState, newGen);
      checkConnection(30, DS2_ID);
@@ -738,7 +759,7 @@
      ds2.stop(); // will need a new broker with another gen id restart it
      bw.shutdown();
      br.shutdown();
      newGen = ds1.getGenerationId();
      newGen = ds1.getGenerationID();
      curState = ds1.getServerState();
      ds2 = createReplicationBroker(DS2_ID, curState, newGen);
      checkConnection(30, DS2_ID);
@@ -798,7 +819,7 @@
    // a backend backed up with a file
    // Clear the backend
    ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
  }
@@ -815,8 +836,8 @@
    super.classCleanUp();
    // Clear the backend
    ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN);
    paranoiaCheck();
  }
@@ -846,7 +867,7 @@
    private short destId = -1; // Server id of server to initialize
    private long nEntries = -1; // Number of entries to send to dest
    private boolean createReader = false;
    /**
     * If the BrokerInitializer is to be used for a lot of entries to send
     * (which is often the case), the reader thread should be enabled to make
@@ -855,7 +876,7 @@
     * method of the broker himself.
     */
    private BrokerReader reader = null;
    /**
     * Creates a broker initializer with a reader
     */
@@ -874,7 +895,7 @@
      this.serverId = serverId;
      this.createReader = createReader;
    }
    /**
     * Initializes a full update session by sending InitializeTargetMsg
     */
@@ -893,15 +914,11 @@
      // Send init msg to warn dest server it is going do be initialized
      RoutableMsg initTargetMsg = null;
      try
      {
        initTargetMsg =
          new InitializeTargetMsg(DN.decode(EXAMPLE_DN), serverId, destId,
      initTargetMsg =
          new InitializeTargetMsg(EXAMPLE_DN, serverId, destId,
          serverId, nEntries);
      } catch (DirectoryException ex)
      {
        fail("Unable to create init message.");
      }
      rb.publish(initTargetMsg);
      // Send top entry for the domain
@@ -951,27 +968,27 @@
    public void runFullUpdate()
    {
      debugInfo("Broker " + serverId + " initializer starting sending entries to server " + destId);
      for(long i = 0 ; i<nEntries ; i++) {
          EntryMsg entryMsg = createNextEntryMsg();
          rb.publish(entryMsg);
      }
      debugInfo("Broker " + serverId + " initializer stopping sending entries");
      debugInfo("Broker " + serverId + " initializer sending EntryDoneMsg");
      DoneMsg doneMsg = new DoneMsg(serverId, destId);
      rb.publish(doneMsg);
      if (createReader)
      {
        reader.shutdown();
      }
      debugInfo("Broker " + serverId + " initializer thread is dying");
    }
  }
  /**
   * Thread for sending a lot of changes through a broker.
   */
@@ -992,6 +1009,7 @@
    private int nChangesSent = 0; // Number of sent changes
    private int nChangesSentLimit = 0;
    ChangeNumberGenerator gen = null;
    private Object sleeper = new Object();
    /**
     * If the BrokerWriter is to be used for a lot of changes to send (which is
     * often the case), the reader thread should be enabled to make the window
@@ -1084,7 +1102,10 @@
        try
        {
          // Writer in pause, sleep a while to let other threads work
          Thread.sleep(1000);
          synchronized(sleeper)
          {
            sleeper.wait(1000);
          }
        } catch (InterruptedException ex)
        {
          /* Don't care */
@@ -1100,6 +1121,10 @@
    {
      suspended.set(true); // If were working
      shutdown.set(true);
      synchronized (sleeper)
      {
        sleeper.notify();
      }
      try
      {
        join();
@@ -1107,7 +1132,7 @@
      {
        /* Don't care */
      }
      // Stop reader if any
      if (reader != null)
      {
@@ -1128,12 +1153,12 @@
      {
        try
        {
          Thread.sleep(1000);
          Thread.sleep(200);
        } catch (InterruptedException ex)
        {
          /* Don't care */
        }
      }
      }
    }
    /**
@@ -1183,7 +1208,7 @@
     */
    public void followAndPause(int nChanges)
    {
      debugInfo("Requested broker writer " + serverId + " to write " + nChanges + " change(s).");
      debugInfo("Requested broker writer " + serverId + " to write " + nChanges + " change(s).");
      pause(); // If however we were already working
      // Initialize counter system variables
@@ -1341,7 +1366,7 @@
   * @param testedValue The value we want to test
   * @param expectedValue The value the tested value should be equal to
   */
  private void sleepAssertStatusEquals(int secTimeout, ReplicationDomain testedValue,
  private void sleepAssertStatusEquals(int secTimeout, LDAPReplicationDomain testedValue,
    ServerStatus expectedValue)
  {
    int nSec = 0;
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/TopologyViewTest.java
@@ -32,7 +32,7 @@
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import static org.opends.server.replication.plugin.ReplicationBroker.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
@@ -114,20 +114,20 @@
  static
  {
    DS2_RU.add("ldap://fake_url_for_ds2");
    DS6_RU.add("ldap://fake_url_for_ds6_A");
    DS6_RU.add("ldap://fake_url_for_ds6_B");
  }
  private int rs1Port = -1;
  private int rs2Port = -1;
  private int rs3Port = -1;
  private ReplicationDomain rd1 = null;
  private ReplicationDomain rd2 = null;
  private ReplicationDomain rd3 = null;
  private ReplicationDomain rd4 = null;
  private ReplicationDomain rd5 = null;
  private ReplicationDomain rd6 = null;
  private LDAPReplicationDomain rd1 = null;
  private LDAPReplicationDomain rd2 = null;
  private LDAPReplicationDomain rd3 = null;
  private LDAPReplicationDomain rd4 = null;
  private LDAPReplicationDomain rd5 = null;
  private LDAPReplicationDomain rd6 = null;
  private ReplicationServer rs1 = null;
  private ReplicationServer rs2 = null;
  private ReplicationServer rs3 = null;
@@ -203,7 +203,7 @@
      rd6.shutdown();
      rd6 = null;
    }
    try
    {
      // Clear any reference to a domain in synchro plugin
@@ -257,7 +257,7 @@
  private void checkConnection(int secTimeout, short dsId, short rsId)
  {
    int rsPort = -1;
    ReplicationDomain rd = null;
    LDAPReplicationDomain rd = null;
    switch (dsId)
    {
      case DS1_ID:
@@ -398,7 +398,7 @@
    }
    return replServers;
  }
  }
  /**
   * Creates a new ReplicationServer.
@@ -447,7 +447,7 @@
   * Creates and starts a new ReplicationDomain with the correct list of
   * know RSs according to DS id
   */
  private ReplicationDomain createReplicationDomain(short dsId)
  private LDAPReplicationDomain createReplicationDomain(short dsId)
  {
    try
    {
@@ -523,7 +523,7 @@
      DomainFakeCfg domainConf =
        new DomainFakeCfg(baseDn, dsId, replServers, assuredType,
        assuredSdLevel, groupId, 0, refUrls);
      ReplicationDomain replicationDomain =
      LDAPReplicationDomain replicationDomain =
        MultimasterReplication.createNewDomain(domainConf);
      replicationDomain.start();
@@ -1047,7 +1047,7 @@
    return new TopoView(dsList, rsList);
  }
  /**
   * Get the topo view each DS in the provided ds list has and compares it
   * with the theoretical topology view that every body should have at the time
@@ -1057,8 +1057,8 @@
  {
   for(short currentDsId : dsIdList)
   {
     ReplicationDomain rd = null;
     LDAPReplicationDomain rd = null;
     switch (currentDsId)
     {
       case DS1_ID:
@@ -1082,7 +1082,7 @@
       default:
         fail("Unknown replication domain server id.");
     }
     /**
      * Get the topo view of the current analyzed DS
      */
@@ -1096,7 +1096,7 @@
       dsList.add(aDsInfo);
     }
     short dsId = rd.getServerId();
     short rsId = rd.getBroker().getRsServerId();
     short rsId = rd.getRsServerId();
     ServerStatus status = rd.getStatus();
     boolean assuredFlag = rd.isAssured();
     AssuredMode assuredMode = rd.getAssuredMode();
@@ -1106,17 +1106,17 @@
     DSInfo dsInfo = new DSInfo(dsId, rsId, TEST_DN_WITH_ROOT_ENTRY_GENID, status, assuredFlag, assuredMode,
       safeDataLevel, groupId, refUrls);
     dsList.add(dsInfo);
     TopoView dsTopoView = new TopoView(dsList, rd.getRsList());
     /**
      * Compare to what is the expected view
      */
     assertEquals(dsTopoView, theoricalTopoView);
   }
  }
  /**
   * Bag class representing a view of the topology at a given time
   * (who is connected to who, what are the config parameters...)
@@ -1124,22 +1124,22 @@
  private class TopoView
  {
    private List<DSInfo> dsList = null;
    private List<RSInfo> rsList = null;
    private List<RSInfo> rsList = null;
    public TopoView(List<DSInfo> dsList, List<RSInfo> rsList)
    {
      assertNotNull(dsList);
      assertNotNull(rsList);
      this.dsList = dsList;
      this.rsList = rsList;
    }
    public boolean equals(Object obj)
    {
      assertNotNull(obj);
      assertFalse(obj.getClass() != this.getClass());
      TopoView topoView = (TopoView) obj;
      // Check dsList
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/ProtocolCompatibilityTest.java
@@ -94,12 +94,12 @@
  @DataProvider(name="createReplServerStartData")
  public Object [][] createReplServerStartData() throws Exception
  {
    DN baseDN = DN.decode("o=test");
    String baseDN = "o=test";
    ServerState state = new ServerState();
    state.update(new ChangeNumber((long)0, 0,(short)0));
    Object[] set1 = new Object[] {(short)1, baseDN, 0, "localhost:8989", state, 0L, (byte)0, 0};
    baseDN = DN.decode("dc=example,dc=com");
    baseDN = "dc=example,dc=com";
    state = new ServerState();
    state.update(new ChangeNumber((long)75, 5,(short)263));
    Object[] set2 = new Object[] {(short)16, baseDN, 100, "anotherHost:1025", state, 1245L, (byte)25, 3456};
@@ -112,7 +112,7 @@
   * using protocol V1 and V2 are working.
   */
  @Test(dataProvider="createReplServerStartData")
  public void replServerStartMsgTest(short serverId, DN baseDN, int window,
  public void replServerStartMsgTest(short serverId, String baseDN, int window,
         String url, ServerState state, long genId, byte groupId, int degTh) throws Exception
  {
    // Create V2 message
@@ -167,7 +167,7 @@
    assertEquals(msg.getGroupId(), v2Msg.getGroupId());
    assertEquals(msg.getDegradedStatusThreshold(), v2Msg.getDegradedStatusThreshold());
  }
  @DataProvider(name = "createAddData")
  public Object[][] createAddData() {
    return new Object[][] {
@@ -250,7 +250,7 @@
    // Check default value for only V2 fields
    assertEquals(newMsg.getAssuredMode(), AssuredMode.SAFE_DATA_MODE);
    assertEquals(newMsg.getSafeDataLevel(), (byte)-1);
    assertEquals(newMsg.getSafeDataLevel(), (byte)1);
    // Set again only V2 fields
    newMsg.setAssuredMode(assuredMode);
@@ -336,7 +336,7 @@
    // Check default value for only V2 fields
    assertEquals(newMsg.getAssuredMode(), AssuredMode.SAFE_DATA_MODE);
    assertEquals(newMsg.getSafeDataLevel(), (byte)-1);
    assertEquals(newMsg.getSafeDataLevel(), (byte)1);
    // Set again only V2 fields
    newMsg.setAssuredMode(assuredMode);
@@ -356,7 +356,7 @@
    assertEquals(msg.getAssuredMode(), v2Msg.getAssuredMode());
    assertEquals(msg.getSafeDataLevel(), v2Msg.getSafeDataLevel());
  }
  /**
   * Build some data for the ModifyMsg test below.
   */
@@ -469,7 +469,7 @@
    // Check default value for only V2 fields
    assertEquals(newMsg.getAssuredMode(), AssuredMode.SAFE_DATA_MODE);
    assertEquals(newMsg.getSafeDataLevel(), (byte)-1);
    assertEquals(newMsg.getSafeDataLevel(), (byte)1);
    // Set again only V2 fields
    newMsg.setAssuredMode(assuredMode);
@@ -503,7 +503,7 @@
                  genModOpBasis.getAttachment(SYNCHROCONTEXT));
    assertEquals(modOpBasis.getModifications(), genModOpBasis.getModifications());
  }
  @DataProvider(name = "createModifyDnData")
  public Object[][] createModifyDnData() {
    
@@ -606,7 +606,7 @@
    // Check default value for only V2 fields
    assertEquals(newMsg.getAssuredMode(), AssuredMode.SAFE_DATA_MODE);
    assertEquals(newMsg.getSafeDataLevel(), (byte)-1);
    assertEquals(newMsg.getSafeDataLevel(), (byte)1);
    assertEquals(modDnOpBasis.getModifications(), mods);
    assertTrue(genModDnOpBasis.getModifications() == null);
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
@@ -26,11 +26,8 @@
 */
package org.opends.server.replication.protocol;
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.assertTrue;
import static org.testng.Assert.fail;
import static org.opends.server.replication.protocol.OperationContext.*;
import static org.testng.Assert.*;
import java.util.Iterator;
import java.util.ArrayList;
@@ -227,7 +224,7 @@
    assertEquals(msg.getAssuredMode(), assuredMode);
    // Check safe data level
    assertTrue(msg.getSafeDataLevel() == -1);
    assertTrue(msg.getSafeDataLevel() == 1);
    msg.setSafeDataLevel(safeDataLevel);
    assertTrue(msg.getSafeDataLevel() == safeDataLevel);
@@ -312,7 +309,7 @@
    assertEquals(op.getRawEntryDN(), mod2.getRawEntryDN());
    // Create an update message from this op
    DeleteMsg updateMsg = (DeleteMsg) UpdateMsg.generateMsg(op);
    DeleteMsg updateMsg = (DeleteMsg) LDAPUpdateMsg.generateMsg(op);
    assertEquals(msg.getChangeNumber(), updateMsg.getChangeNumber());
  }
@@ -415,7 +412,7 @@
    assertEquals(moddn1.getModifications(), moddn2.getModifications());
    // Create an update message from this op
    ModifyDNMsg updateMsg = (ModifyDNMsg) UpdateMsg.generateMsg(localOp);
    ModifyDNMsg updateMsg = (ModifyDNMsg) LDAPUpdateMsg.generateMsg(localOp);
    assertEquals(msg.getChangeNumber(), updateMsg.getChangeNumber());
  }
@@ -518,7 +515,7 @@
    // Create an update message from this op
    AddMsg updateMsg = (AddMsg) UpdateMsg.generateMsg(addOp);
    AddMsg updateMsg = (AddMsg) LDAPUpdateMsg.generateMsg(addOp);
    assertEquals(msg.getChangeNumber(), updateMsg.getChangeNumber());
  }
@@ -615,12 +612,11 @@
  @DataProvider(name="createServerStartData")
  public Object [][] createServerStartData() throws Exception
  {
    DN baseDN = DN.decode(TEST_ROOT_DN_STRING);
    String baseDN = TEST_ROOT_DN_STRING;
    ServerState state = new ServerState();
    state.update(new ChangeNumber((long)0, 0,(short)0));
    Object[] set1 = new Object[] {(short)1, baseDN, 0, state, 0L, false, (byte)0};
    baseDN = DN.decode(TEST_ROOT_DN_STRING);
    state = new ServerState();
    state.update(new ChangeNumber((long)75, 5,(short)263));
    Object[] set2 = new Object[] {(short)16, baseDN, 100, state, 1248L, true, (byte)31};
@@ -633,7 +629,7 @@
   * by checking that : msg == new ServerStartMsg(msg.getBytes()).
   */
  @Test(dataProvider="createServerStartData")
  public void serverStartMsgTest(short serverId, DN baseDN, int window,
  public void serverStartMsgTest(short serverId, String baseDN, int window,
         ServerState state, long genId, boolean sslEncryption, byte groupId) throws Exception
  {
    ServerStartMsg msg = new ServerStartMsg(serverId, baseDN,
@@ -659,12 +655,11 @@
  @DataProvider(name="createReplServerStartData")
  public Object [][] createReplServerStartData() throws Exception
  {
    DN baseDN = DN.decode(TEST_ROOT_DN_STRING);
    String baseDN = TEST_ROOT_DN_STRING;
    ServerState state = new ServerState();
    state.update(new ChangeNumber((long)0, 0,(short)0));
    Object[] set1 = new Object[] {(short)1, baseDN, 0, "localhost:8989", state, 0L, (byte)0, 0};
    baseDN = DN.decode(TEST_ROOT_DN_STRING);
    state = new ServerState();
    state.update(new ChangeNumber((long)75, 5,(short)263));
    Object[] set2 = new Object[] {(short)16, baseDN, 100, "anotherHost:1025", state, 1245L, (byte)25, 3456};
@@ -677,7 +672,7 @@
   * by checking that : msg == new ReplServerStartMsg(msg.getBytes()).
   */
  @Test(dataProvider="createReplServerStartData")
  public void replServerStartMsgTest(short serverId, DN baseDN, int window,
  public void replServerStartMsgTest(short serverId, String baseDN, int window,
         String url, ServerState state, long genId, byte groupId, int degTh) throws Exception
  {
    ReplServerStartMsg msg = new ReplServerStartMsg(serverId,
@@ -734,7 +729,7 @@
    List<String> urls3 = new ArrayList<String>();
    urls3.add("ldaps://host:port/dc=foo??sub?(sn=One Entry)");
    List<String> urls4 = new ArrayList<String>();
    urls4.add("ldaps://host:port/dc=foobar1??sub?(sn=Another Entry 1)");
    urls4.add("ldaps://host:port/dc=foobar2??sub?(sn=Another Entry 2)");
@@ -747,7 +742,7 @@
    DSInfo dsInfo3 = new DSInfo((short)2436, (short)591, (long)0, ServerStatus.NORMAL_STATUS,
      false, AssuredMode.SAFE_READ_MODE, (byte)17, (byte)0, urls3);
    DSInfo dsInfo4 = new DSInfo((short)415, (short)146, (long)0, ServerStatus.BAD_GEN_ID_STATUS,
      true, AssuredMode.SAFE_DATA_MODE, (byte)2, (byte)15, urls4);
@@ -862,7 +857,7 @@
    assertTrue(msg.getSafeDataLevel() == newMsg.getSafeDataLevel());
    assertEquals(msg.getReferralsURLs(), newMsg.getReferralsURLs());
  }
  /**
   * Provider for the ChangeStatusMsg test
   */
@@ -897,6 +892,7 @@
  {
    HeartbeatMsg msg = new HeartbeatMsg();
    HeartbeatMsg newMsg = new HeartbeatMsg(msg.getBytes());
    assertNotNull(newMsg);
  }
  /**
@@ -909,7 +905,7 @@
    ResetGenerationIdMsg newMsg = new ResetGenerationIdMsg(msg.getBytes());
    assertEquals(msg.getGenerationId(), newMsg.getGenerationId());
  }
  /**
   * Test MonitorRequestMsg encoding and decoding.
   */
@@ -918,6 +914,8 @@
  {
    MonitorRequestMsg msg = new MonitorRequestMsg((short)1,(short)2);
    MonitorRequestMsg newMsg = new MonitorRequestMsg(msg.getBytes());
    assertEquals(newMsg.getDestination(), 2);
    assertEquals(newMsg.getsenderID(), 1);
  }
  /**
@@ -963,14 +961,14 @@
    msg.setServerState(sid1, s1, now+1, true);
    msg.setServerState(sid2, s2, now+2, true);
    msg.setServerState(sid3, s3, now+3, false);
    byte[] b = msg.getBytes();
    MonitorMsg newMsg = new MonitorMsg(b);
    assertEquals(rsState, msg.getReplServerDbState());
    assertEquals(newMsg.getReplServerDbState().toString(),
    assertEquals(newMsg.getReplServerDbState().toString(),
        msg.getReplServerDbState().toString());
    Iterator<Short> it = newMsg.ldapIterator();
    while (it.hasNext())
    {
@@ -983,7 +981,7 @@
      }
      else if (sid == sid2)
      {
        assertEquals(s.toString(), s2.toString());
        assertEquals(s.toString(), s2.toString());
        assertEquals((Long)(now+2), newMsg.getLDAPApproxFirstMissingDate(sid), "");
      }
      else
@@ -1025,7 +1023,7 @@
        "objectclass: top\n" +
        "objectclass: ds-task\n" +
        "objectclass: ds-task-initialize\n" +
        "ds-task-class-name: org.opends.server.tasks.InitializeTask\n" +
        "ds-task-class-name: org.opends.server.replication.api.InitializeTask\n" +
        "ds-task-initialize-domain-dn: " + TEST_ROOT_DN_STRING  + "\n" +
        "ds-task-initialize-source: 1\n");
    short sender = 1;
@@ -1047,7 +1045,7 @@
    short sender = 1;
    short target = 2;
    InitializeRequestMsg msg = new InitializeRequestMsg(
        DN.decode(TEST_ROOT_DN_STRING), sender, target);
        TEST_ROOT_DN_STRING, sender, target);
    InitializeRequestMsg newMsg = new InitializeRequestMsg(msg.getBytes());
    assertEquals(msg.getsenderID(), newMsg.getsenderID());
    assertEquals(msg.getDestination(), newMsg.getDestination());
@@ -1064,10 +1062,9 @@
    short targetID = 2;
    short requestorID = 3;
    long entryCount = 4;
    DN baseDN = DN.decode(TEST_ROOT_DN_STRING);
    InitializeTargetMsg msg = new InitializeTargetMsg(
        baseDN, senderID, targetID, requestorID, entryCount);
        TEST_ROOT_DN_STRING, senderID, targetID, requestorID, entryCount);
    InitializeTargetMsg newMsg = new InitializeTargetMsg(msg.getBytes());
    assertEquals(msg.getsenderID(), newMsg.getsenderID());
    assertEquals(msg.getDestination(), newMsg.getDestination());
@@ -1079,7 +1076,7 @@
    assertEquals(targetID, newMsg.getDestination());
    assertEquals(requestorID, newMsg.getRequestorID());
    assertEquals(entryCount, newMsg.getEntryCount());
    assertTrue(baseDN.equals(newMsg.getBaseDN())) ;
    assertTrue(TEST_ROOT_DN_STRING.equals(newMsg.getBaseDN())) ;
  }
@@ -1108,4 +1105,19 @@
    assertEquals(msg.getMsgID(), newMsg.getMsgID());
    assertEquals(msg.getDetails(), newMsg.getDetails());
  }
  /**
   * Test Generic UpdateMsg
   */
  @Test
  public void UpdateMsgTest() throws Exception
  {
    final String test = "string used for test";
    UpdateMsg msg =
      new UpdateMsg(
          new ChangeNumber((long) 1, 2 , (short)3),
          test.getBytes());
    UpdateMsg newMsg = new UpdateMsg(msg.getBytes());
    assertEquals(test.getBytes(), newMsg.getPayload());
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/DbHandlerTest.java
@@ -34,7 +34,6 @@
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.types.DN;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
import static org.opends.server.TestCaseUtils.*;
@@ -79,7 +78,7 @@
      dbEnv = new ReplicationDbEnv(path, replicationServer);
      handler = new DbHandler((short) 1, DN.decode(TEST_ROOT_DN_STRING),
      handler = new DbHandler((short) 1, TEST_ROOT_DN_STRING,
        replicationServer, dbEnv, 5000);
      ChangeNumberGenerator gen = new ChangeNumberGenerator((short) 1, 0);
@@ -173,7 +172,7 @@
      dbEnv = new ReplicationDbEnv(path, replicationServer);
      handler =
        new DbHandler((short) 1, DN.decode(TEST_ROOT_DN_STRING),
        new DbHandler((short) 1, TEST_ROOT_DN_STRING,
        replicationServer, dbEnv, 5000);
      // Creates changes added to the dbHandler
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/MonitorTest.java
@@ -50,9 +50,9 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.plugin.ReplicationDomain;
import org.opends.server.replication.plugin.LDAPReplicationDomain;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.ReplicationMsg;
import org.opends.server.replication.protocol.SocketSession;
@@ -93,7 +93,7 @@
  private ReplicationServer replServer2 = null;
  private ReplicationServer replServer3 = null;
  private boolean emptyOldChanges = true;
  ReplicationDomain replDomain = null;
  LDAPReplicationDomain replDomain = null;
  SocketSession ssSession = null;
  boolean ssShutdownRequested = false;
  protected String[] updatedEntries;
@@ -240,7 +240,7 @@
        "Unable to add the synchronized server");
      configEntryList.add(synchroServerEntry.getDN());
      replDomain = ReplicationDomain.retrievesReplicationDomain(baseDn);
      replDomain = LDAPReplicationDomain.retrievesReplicationDomain(baseDn);
      if (replDomain != null)
      {
@@ -393,7 +393,6 @@
      debugInfo("Connecting DS to replServer1");
      connectServer1ToChangelog(changelog1ID);
      Thread.sleep(1500);
      try
      {
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerDynamicConfTest.java
@@ -32,13 +32,13 @@
import org.opends.server.TestCaseUtils;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.types.DN;
import org.testng.annotations.Test;
import static org.opends.server.TestCaseUtils.*;
/**
 * Tests that we can dynamically modify the configuration of replicationServer
 * Tests that we can dynamically modify the configuration of replicationServer
 */
public class ReplicationServerDynamicConfTest extends ReplicationTestCase
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java
@@ -63,12 +63,12 @@
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.common.ServerStatus;
import org.opends.server.replication.plugin.MultimasterReplication;
import org.opends.server.replication.plugin.ReplicationBroker;
import org.opends.server.replication.plugin.ReplicationServerListener;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.DeleteMsg;
@@ -192,14 +192,12 @@
  {
    replicationServer.clearDb();
    changelogBasic();
    multipleWriterMultipleReader();
    newClientLateServer1();
    newClient();
    newClientWithFirstChanges();
    newClientWithChangefromServer1();
    newClientWithChangefromServer2();
    newClientWithUnknownChanges();
    oneWriterMultipleReader();
    changelogChaining();
    stopChangelog();
    exportBackend();
@@ -207,7 +205,7 @@
    windowProbeTest();
    replicationServerConnected();
  }
  /**
   * This test allows to check the behavior of the Replication Server
   * when the DS disconnect and reconnect again.
@@ -255,7 +253,7 @@
      server2 = openReplicationSession(
          DN.decode(TEST_ROOT_DN_STRING), (short) 2, 100, replicationServerPort,
          1000, true);
      assertTrue(server1.isConnected());
      assertTrue(server2.isConnected());
@@ -328,10 +326,6 @@
                      "other-uid");
      server2.publish(msg);
      msg2 = server1.receive();
      if (!(msg2 instanceof TopologyMsg))
        fail("ReplicationServer basic : incorrect message type received: " +
          msg2.getClass().toString() + ": content: " + msg2.toString());
      msg2 = server1.receive();
      server1.updateWindowAfterReplay();
      if (msg2 instanceof DeleteMsg)
      {
@@ -359,7 +353,7 @@
      else
        fail("ReplicationServer basic : incorrect message type received: " +
          msg2.getClass().toString() + ": content: " + msg2.toString());
      debugInfo("Ending changelogBasic");
    }
    finally
@@ -564,9 +558,14 @@
   * This test is configured by a relatively low stress
   * but can be changed using TOTAL_MSG and CLIENT_THREADS consts.
   */
  private void oneWriterMultipleReader() throws Exception
  @Test(enabled=true)
  public void oneWriterMultipleReader() throws Exception
  {
    debugInfo("Starting oneWriterMultipleReader");
    replicationServer.clearDb();
    TestCaseUtils.initializeTestBackend(true);
    ReplicationBroker server = null;
    BrokerReader reader = null;
    int TOTAL_MSG = 1000;     // number of messages to send during the test
@@ -638,6 +637,9 @@
        if (clientBroker[i] != null)
          clientBroker[i].stop();
      }
      replicationServer.clearDb();
      TestCaseUtils.initializeTestBackend(true);
    }
  }
@@ -652,7 +654,8 @@
   * This test is configured for a relatively low stress
   * but can be changed using TOTAL_MSG and THREADS consts.
   */
  private void multipleWriterMultipleReader() throws Exception
  @Test()
  public void multipleWriterMultipleReader() throws Exception
  {
    debugInfo("Starting multipleWriterMultipleReader");
    final int TOTAL_MSG = 1000;   // number of messages to send during the test
@@ -663,6 +666,9 @@
    BrokerReader reader[] = new BrokerReader[THREADS];
    ReplicationBroker broker[] = new ReplicationBroker[THREADS];
    replicationServer.clearDb();
    TestCaseUtils.initializeTestBackend(true);
    try
    {
      /*
@@ -711,6 +717,9 @@
        if (broker[i] != null)
          broker[i].stop();
      }
      replicationServer.clearDb();
      TestCaseUtils.initializeTestBackend(true);
    }
  }
@@ -789,7 +798,7 @@
        //              client2 will be created later
        broker1 = openReplicationSession(DN.decode(TEST_ROOT_DN_STRING),
             brokerIds[0], 100, changelogPorts[0], 1000, !emptyOldChanges);
        assertTrue(broker1.isConnected());
        if (itest == 0)
@@ -975,7 +984,7 @@
    {
      // send a ServerStartMsg with an empty ServerState.
      ServerStartMsg msg =
        new ServerStartMsg((short) 1723, DN.decode(TEST_ROOT_DN_STRING),
        new ServerStartMsg((short) 1723, TEST_ROOT_DN_STRING,
            0, 0, 0, 0, WINDOW, (long) 5000, new ServerState(),
            ProtocolVersion.getCurrentVersion(), 0, sslEncryption, (byte)-1);
      session.publish(msg);
@@ -1021,7 +1030,7 @@
      // received.
      DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
      msg = new ServerStartMsg(
          (short) 1724, baseDn,
          (short) 1724, TEST_ROOT_DN_STRING,
          0, 0, 0, 0, WINDOW, (long) 5000, replServerState,
          ProtocolVersion.getCurrentVersion(),
          ReplicationTestCase.getGenerationId(baseDn),
@@ -1072,7 +1081,7 @@
   * @throws Exception If the environment could not be set up.
   */
  @AfterClass
  public void classCleanUp() throws Exception
  {
    callParanoiaCheck = false;
@@ -1410,12 +1419,7 @@
    * Testing searches on the backend of the replication server.
    * @throws Exception
    */
   // TODO: this test disabled as testReplicationBackendACIs() is failing
   // : anonymous search returns entries from replication backend whereas it
   // should not. Probably a previous test in the nightlytests suite is
   // removing/modifying some ACIs...When problem foound, we have to re-enable
   // this test.
   @Test(enabled=false)
   @Test(enabled=true)
   public void searchBackend() throws Exception
   {
     debugInfo("Starting searchBackend");
@@ -1494,7 +1498,13 @@
           LDAPFilter.decode("(changetype=*)"));
       assertEquals(op.getResultCode(), ResultCode.NO_SUCH_OBJECT);
       testReplicationBackendACIs();
       // TODO:  testReplicationBackendACIs() is disabled because it
       // is currently failing when run in the nightly target.
       // anonymous search returns entries from replication backend whereas it
       // should not. Probably a previous test in the nightlytests suite is
       // removing/modifying some ACIs...When problem foound, we have to re-enable
       // this test.
       // testReplicationBackendACIs();
       // General search
       op = connection.processSearch(
@@ -1555,10 +1565,10 @@
       assertEquals(op.getSearchEntries().size(), 1);
       /*
        * It would be nice to be have the abilities to search for
        * entries in the replication backend using the DN on which the
        * It would be nice to be have the abilities to search for
        * entries in the replication backend using the DN on which the
        * operation was done as the search criteria.
        * This is not possible yet, this part of the test is therefore
        * This is not possible yet, this part of the test is therefore
        * disabled.
        *
        * debugInfo("Query / searchBase");
@@ -1604,7 +1614,7 @@
             attrs2);
       assertEquals(op.getResultCode(), ResultCode.SUCCESS);
       assertEquals(op.getSearchEntries().size(), 5);
       debugInfo("Successfully ending searchBackend");
     } finally {
@@ -1759,7 +1769,7 @@
         broker2 = openReplicationSession(DN.decode(TEST_ROOT_DN_STRING),
              brokerIds[1], 100, changelogPorts[1], 1000, emptyOldChanges);
         assertTrue(broker1.isConnected());
         assertTrue(broker2.isConnected());
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeReplicationDomain.java
New file
@@ -0,0 +1,149 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.service;
import org.opends.server.types.ResultCode;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import org.opends.server.config.ConfigException;
import java.util.Collection;
import java.io.InputStream;
import java.io.OutputStream;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.types.DirectoryException;
import static org.opends.messages.ReplicationMessages.*;
/**
 * This class is the minimum implementation of a Concrete ReplicationDomain
 * used to test the Generic Replication Service.
 */
public class FakeReplicationDomain extends ReplicationDomain
{
  // A blocking queue that is used to send the UpdateMsg received from
  // the Replication Service.
  BlockingQueue<UpdateMsg> queue = null;
  // A string that will be exported should exportBackend be called.
  String exportString = null;
  // A StringBuilder that will be used to build a build a new String should the
  // import be called.
  StringBuilder importString = null;
  private int exportedEntryCount;
  public FakeReplicationDomain(
      String serviceID,
      short serverID,
      Collection<String> replicationServers,
      int window,
      long heartbeatInterval,
      BlockingQueue<UpdateMsg> queue) throws ConfigException
  {
    super(serviceID, serverID);
    startPublishService(replicationServers, window, heartbeatInterval);
    startListenService();
    this.queue = queue;
  }
  public FakeReplicationDomain(
      String serviceID,
      short serverID,
      Collection<String> replicationServers,
      int window,
      long heartbeatInterval,
      String exportString,
      StringBuilder importString,
      int exportedEntryCount) throws ConfigException
  {
    super(serviceID, serverID);
    startPublishService(replicationServers, window, heartbeatInterval);
    startListenService();
    this.exportString = exportString;
    this.importString = importString;
    this.exportedEntryCount = exportedEntryCount;
  }
  @Override
  public long countEntries() throws DirectoryException
  {
    return exportedEntryCount;
  }
  @Override
  protected void exportBackend(OutputStream output) throws DirectoryException
  {
    try
    {
      output.write(exportString.getBytes());
      output.flush();
      output.close();
    }
    catch (IOException e)
    {
      throw new DirectoryException(ResultCode.OPERATIONS_ERROR,
          ERR_BACKEND_EXPORT_ENTRY.get("", ""));
    }
  }
  @Override
  public long getGenerationID()
  {
    return 1;
  }
  @Override
  protected void importBackend(InputStream input) throws DirectoryException
  {
    byte[] buffer = new byte[1000];
    int ret;
    do
    {
      try
      {
        ret = input.read(buffer, 0, 1000);
      } catch (IOException e)
      {
        throw new DirectoryException(
            ResultCode.OPERATIONS_ERROR,
            ERR_BACKEND_EXPORT_ENTRY.get("", ""));
      }
      importString.append(new String(buffer, 0, ret));
    }
    while (ret >= 0);
  }
  @Override
  public boolean processUpdate(UpdateMsg updateMsg)
  {
    if (queue != null)
      queue.add(updateMsg);
    return true;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/FakeStressReplicationDomain.java
New file
@@ -0,0 +1,159 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.service;
import org.opends.server.types.ResultCode;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import org.opends.server.config.ConfigException;
import java.util.Collection;
import java.io.InputStream;
import java.io.OutputStream;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.types.DirectoryException;
import static org.opends.messages.ReplicationMessages.*;
/**
 * This class is the minimum implementation of a Concrete ReplicationDomain
 * used to test the Generic Replication Service.
 */
public class FakeStressReplicationDomain extends ReplicationDomain
{
  // A blocking queue that is used to send the UpdateMsg received from
  // the Replication Service.
  BlockingQueue<UpdateMsg> queue = null;
  // A string that will be exported should exportBackend be called.
  String exportString = null;
  // A StringBuilder that will be used to build a build a new String should the
  // import be called.
  StringBuilder importString = null;
  public FakeStressReplicationDomain(
      String serviceID,
      short serverID,
      Collection<String> replicationServers,
      int window,
      long heartbeatInterval,
      BlockingQueue<UpdateMsg> queue) throws ConfigException
  {
    super(serviceID, serverID);
    startPublishService(replicationServers, window, heartbeatInterval);
    startListenService();
    this.queue = queue;
  }
  public FakeStressReplicationDomain(
      String serviceID,
      short serverID,
      Collection<String> replicationServers,
      int window,
      long heartbeatInterval,
      String exportString,
      StringBuilder importString) throws ConfigException
  {
    super(serviceID, serverID);
    startPublishService(replicationServers, window, heartbeatInterval);
    startListenService();
    this.exportString = exportString;
    this.importString = importString;
  }
  final int IMPORT_SIZE = 100000000;
  @Override
  public long countEntries() throws DirectoryException
  {
    return IMPORT_SIZE;
  }
  @Override
  protected void exportBackend(OutputStream output) throws DirectoryException
  {
    System.out.println("export started");
    try
    {
      for (int i=0; i< IMPORT_SIZE; i++)
      {
        output.write("this is a long key like a dn or something similar: value\n\n".getBytes());
      }
      output.flush();
      output.close();
    }
    catch (IOException e)
    {
      throw new DirectoryException(ResultCode.OPERATIONS_ERROR,
          ERR_BACKEND_EXPORT_ENTRY.get("", ""));
    }
    System.out.println("export finished");
  }
  @Override
  public long getGenerationID()
  {
    return 1;
  }
  @Override
  protected void importBackend(InputStream input) throws DirectoryException
  {
    long startDate = System.currentTimeMillis();
    System.out.println("import started : " + startDate);
    byte[] buffer = new byte[10000];
    int ret;
    int count = 0;
    do
    {
      try
      {
        ret = input.read(buffer, 0, 10000);
      } catch (IOException e)
      {
        e.printStackTrace();
        throw new DirectoryException(
            ResultCode.OPERATIONS_ERROR,
            ERR_BACKEND_EXPORT_ENTRY.get("", ""));
      }
      count++;
    }
    while (ret >= 0);
    long endDate = System.currentTimeMillis();
    System.out.println("import end : " + endDate);
    System.out.println("import duration (sec): " + (endDate - startDate) / 1000);
    System.out.println("import count: " + count);
  }
  @Override
  public boolean processUpdate(UpdateMsg updateMsg)
  {
    if (queue != null)
      queue.add(updateMsg);
    return true;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/service/ReplicationDomainTest.java
New file
@@ -0,0 +1,317 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.replication.service;
import static org.testng.Assert.*;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingQueue;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.opends.server.TestCaseUtils;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.DSInfo;
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.replication.server.ReplServerFakeConfiguration;
import org.opends.server.replication.server.ReplicationServer;
import org.testng.annotations.Test;
/**
 * Test the Generic Replication Service.
 */
public class ReplicationDomainTest extends ReplicationTestCase
{
  /**
   * Test that a ReplicationDomain is able to publish and receive UpdateMsg.
   */
  @Test(enabled=true)
  public void publishAndReceive() throws Exception
  {
    String testService = "test";
    ReplicationServer replServer = null;
    int replServerID = 10;
    FakeReplicationDomain domain1 = null;
    FakeReplicationDomain domain2 = null;
    try
    {
      // find  a free port for the replicationServer
      ServerSocket socket = TestCaseUtils.bindFreePort();
      int replServerPort = socket.getLocalPort();
      socket.close();
      ReplServerFakeConfiguration conf =
        new ReplServerFakeConfiguration(
            replServerPort, "ReplicationDomainTestDb",
            0, replServerID, 0, 100, null);
      replServer = new ReplicationServer(conf);
      ArrayList<String> servers = new ArrayList<String>(1);
      servers.add("localhost:" + replServerPort);
      BlockingQueue<UpdateMsg> rcvQueue1 = new LinkedBlockingQueue<UpdateMsg>();
      domain1 = new FakeReplicationDomain(
          testService, (short) 1, servers, 100, 1000, rcvQueue1);
      BlockingQueue<UpdateMsg> rcvQueue2 = new LinkedBlockingQueue<UpdateMsg>();
      domain2 = new FakeReplicationDomain(
          testService, (short) 2, servers, 100, 1000, rcvQueue2);
      /*
       * Publish a message from domain1,
       * Check that domain2 receives it shortly after.
       */
      byte[] test = {1, 2, 3 ,4, 0, 1, 2, 3, 4, 5};
      domain1.publish(test);
      UpdateMsg rcvdMsg = rcvQueue2.poll(1, TimeUnit.SECONDS);
      assertNotNull(rcvdMsg);
      assertEquals(test, rcvdMsg.getPayload());
    }
    finally
    {
      if (domain1 != null)
        domain1.disableService();
      if (domain2 != null)
        domain2.disableService();
      if (replServer != null)
        replServer.remove();
    }
  }
  /**
   * Test that a ReplicationDomain is able to export and import its database.
   */
  @Test(enabled=true)
  public void exportAndImport() throws Exception
  {
    final int ENTRYCOUNT=5000;
    String testService = "test";
    ReplicationServer replServer = null;
    int replServerID = 11;
    FakeReplicationDomain domain1 = null;
    FakeReplicationDomain domain2 = null;
    try
    {
      // find  a free port for the replicationServer
      ServerSocket socket = TestCaseUtils.bindFreePort();
      int replServerPort = socket.getLocalPort();
      socket.close();
      ReplServerFakeConfiguration conf =
        new ReplServerFakeConfiguration(
            replServerPort, "ReplicationDomainTestDb",
            0, replServerID, 0, 100, null);
      replServer = new ReplicationServer(conf);
      ArrayList<String> servers = new ArrayList<String>(1);
      servers.add("localhost:" + replServerPort);
      StringBuilder exportedDataBuilder = new StringBuilder();
      for (int i =0; i<ENTRYCOUNT; i++)
      {
        exportedDataBuilder.append("key : value"+i+"\n\n");
      }
      String exportedData=exportedDataBuilder.toString();
      domain1 = new FakeReplicationDomain(
          testService, (short) 1, servers,
          100, 0, exportedData, null, ENTRYCOUNT);
      StringBuilder importedData = new StringBuilder();
      domain2 = new FakeReplicationDomain(
          testService, (short) 2, servers, 100, 0,
          null, importedData, 0);
      /*
       * Trigger a total update from domain1 to domain2.
       * Check that the exported data is correctly received on domain2.
       */
      for (DSInfo remoteDS : domain2.getDsList())
      {
        if (remoteDS.getDsId() != domain2.getServerId())
        {
          domain2.initializeFromRemote(remoteDS.getDsId() , null);
          break;
        }
      }
      int count = 0;
      while ((importedData.length() < exportedData.length()) && (count < 500))
      {
        count ++;
        Thread.sleep(100);
      }
      assertTrue(domain2.getLeftEntryCount() == 0,
          "LeftEntryCount for export is " + domain2.getLeftEntryCount());
      assertTrue(domain1.getLeftEntryCount() == 0,
          "LeftEntryCount for import is " + domain1.getLeftEntryCount());
      assertEquals(importedData.length(), exportedData.length());
      assertEquals(importedData.toString(), exportedData);
    }
    finally
    {
      if (domain1 != null)
        domain1.disableService();
      if (domain2 != null)
        domain2.disableService();
      if (replServer != null)
        replServer.remove();
    }
  }
  /**
   * Sender side of the Total Update Perf test.
   * The goal of this test is to measure the performance
   * of the total update code.
   * It is not intended to be run as part of the daily unit test but
   * should only be used manually by developer in need of testing the
   * performance improvement or non-regression of the total update code.
   * Use this test in combination with the receiverInitialize() :
   *   - enable the test
   *   - start the senderInitialize first using
   *     ./build.sh \
   *        -Dorg.opends.test.suppressOutput=false \
   *        -Dtest.methods=org.opends.server.replication.service.ReplicationDomainTest.senderInitialize test
   *   - start the receiverInitialize second.
   *   - you may want to change  HOST1 and HOST2 to use 2 different hosts
   *     if you don't want to do a loopback test.
   *   - don't forget to disable again the tests after running them
   */
  final String HOST1 = "localhost:";
  final String HOST2 = "localhost:";
  final int SENDERPORT = 10102;
  final int RECEIVERPORT = 10101;
  @Test(enabled=false)
  public void senderInitialize() throws Exception
  {
    String testService = "test";
    ReplicationServer replServer = null;
    int replServerID = 12;
    FakeStressReplicationDomain domain1 = null;
    try
    {
      TreeSet<String> servers = new TreeSet<String>();
      servers.add(HOST1 + SENDERPORT);
      servers.add(HOST2 + RECEIVERPORT);
      ReplServerFakeConfiguration conf =
        new ReplServerFakeConfiguration(
            SENDERPORT, "ReplicationDomainTestDb",
            0, replServerID, 0, 100, servers);
      replServer = new ReplicationServer(conf);
      BlockingQueue<UpdateMsg> rcvQueue1 = new LinkedBlockingQueue<UpdateMsg>();
      domain1 = new FakeStressReplicationDomain(
          testService, (short) 2, servers, 100, 1000, rcvQueue1);
      System.out.println("waiting");
      Thread.sleep(1000000000);
    }
    finally
    {
      if (domain1 != null)
        domain1.disableService();
      if (replServer != null)
        replServer.remove();
    }
  }
  /**
   * See comments in senderInitialize() above
   */
  @Test(enabled=false)
  public void receiverInitialize() throws Exception
  {
    String testService = "test";
    ReplicationServer replServer = null;
    int replServerID = 11;
    FakeStressReplicationDomain domain1 = null;
    try
    {
      TreeSet<String> servers = new TreeSet<String>();
      servers.add(HOST1 + SENDERPORT);
      servers.add(HOST2 + RECEIVERPORT);
      ReplServerFakeConfiguration conf =
        new ReplServerFakeConfiguration(
            RECEIVERPORT, "ReplicationDomainTestDb",
            0, replServerID, 0, 100, servers);
      replServer = new ReplicationServer(conf);
      BlockingQueue<UpdateMsg> rcvQueue1 = new LinkedBlockingQueue<UpdateMsg>();
      domain1 = new FakeStressReplicationDomain(
          testService, (short) 1, servers, 100, 100000, rcvQueue1);
      /*
       * Trigger a total update from domain1 to domain2.
       * Check that the exported data is correctly received on domain2.
       */
      boolean alone = true;
      while (alone)
      {
        for (DSInfo remoteDS : domain1.getDsList())
        {
          if (remoteDS.getDsId() != domain1.getServerId())
          {
            alone = false;
            domain1.initializeFromRemote(remoteDS.getDsId() , null);
            break;
          }
        }
        if (alone)
        {
          System.out.println("trying...");
          Thread.sleep(1000);
        }
      }
      System.out.println("waiting");
      Thread.sleep(10000000);
    }
    finally
    {
      if (domain1 != null)
        domain1.disableService();
      if (replServer != null)
        replServer.remove();
    }
  }
}