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

Jean-Noel Rouvignac
15.57.2014 f114ca84d5ee2931d00dcd756bcac1cdddfafcea
Code cleanup.


ExternalChangeLogTest.java:
Fixed (?) random failures by comparing LDIF values (rightly line ordering insensitive) rather than String values (wrongly line ordering sensitive).
Added checkLDIF() and toLDIFEntries().
In checkValues(), changed assert.

LDAPReplicationDomain.java:
Changed shutdown field from boolean to AtomicBoolean.
Removed shutdown field and initiateShutdown() from RSUpdater.
In buildAndPublishMissingChanges(), removed AtomicBoolean parameter.

HistoricalCsnOrderingTest.java:
Consequence of the change to LDAPReplicationDomain.buildAndPublishMissingChanges().
Reduced visibilities + added final keyword to fields.

DomainFakeCfg.java:
Removed 2 unused ctors, addECLDomainAddListener(), removeECLDomainAddListener(), addECLDomainDeleteListener(), removeECLDomainDeleteListener().

CryptoManagerImpl.java:
Fixed javadocs.
Removed useless StringBuilder uses.
5 files modified
512 ■■■■ changed files
opends/src/server/org/opends/server/crypto/CryptoManagerImpl.java 245 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java 42 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java 116 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalCsnOrderingTest.java 15 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java 94 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/crypto/CryptoManagerImpl.java
@@ -23,13 +23,10 @@
 *
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2009 Parametric Technology Corporation (PTC)
 *      Portions Copyright 2011 ForgeRock AS
 *      Portions Copyright 2011-2014 ForgeRock AS
 */
package org.opends.server.crypto;
import org.opends.messages.Message;
import static org.opends.messages.CoreMessages.*;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -38,54 +35,54 @@
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import java.text.ParseException;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import org.opends.admin.ads.ADSContext;
import org.opends.server.admin.std.server.CryptoManagerCfg;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.CryptoManagerCfg;
import org.opends.server.api.Backend;
import org.opends.server.backends.TrustStoreBackend;
import org.opends.server.config.ConfigException;
import org.opends.server.config.ConfigConstants;
import org.opends.server.core.DirectoryServer;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.util.StaticUtils.*;
import org.opends.server.util.Validator;
import org.opends.server.util.SelectableCertificateKeyManager;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.Base64;
import org.opends.server.util.ServerConstants;
import static org.opends.server.util.ServerConstants.OC_TOP;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.schema.BinarySyntax;
import org.opends.server.schema.DirectoryStringSyntax;
import org.opends.server.schema.IntegerSyntax;
import org.opends.server.schema.BinarySyntax;
import org.opends.server.tools.LDAPConnection;
import org.opends.server.tools.LDAPConnectionOptions;
import org.opends.server.tools.LDAPReader;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.*;
import org.opends.server.util.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 This class implements the Directory Server cryptographic framework,
@@ -129,87 +126,93 @@
  private static ObjectClass   ocCipherKey;
  private static ObjectClass   ocMacKey;
  // The DN of the local truststore backend.
  /** The DN of the local truststore backend. */
  private static DN localTruststoreDN;
  // The DN of the ADS instance keys container.
  /** The DN of the ADS instance keys container. */
  private static DN instanceKeysDN;
  // The DN of the ADS secret keys container.
  /** The DN of the ADS secret keys container. */
  private static DN secretKeysDN;
  // The DN of the ADS servers container.
  /** The DN of the ADS servers container. */
  private static DN serversDN;
  // Indicates whether the schema references have been initialized.
  /** Indicates whether the schema references have been initialized. */
  private static boolean schemaInitDone = false;
  // The secure random number generator used for key generation,
  // initialization vector PRNG seed...
  /**
   * The secure random number generator used for key generation, initialization
   * vector PRNG seed...
   */
  private static final SecureRandom secureRandom = new SecureRandom();
  // The random number generator used for initialization vector
  // production.
  /** The random number generator used for initialization vector production. */
  private static final Random pseudoRandom
          = new Random(secureRandom.nextLong());
  // The first byte in any ciphertext produced by CryptoManager is the
  // prologue version. At present, this constant is both the version written
  // and the expected version. If a new version is introduced (e.g., to allow
  // embedding the HMAC key identifier and signature in a signed backup) the
  // prologue version will likely need to be configurable at the granularity
  // of the CryptoManager client (e.g., password encryption might use version 1,
  // while signed backups might use version 2.
  /**
   * The first byte in any ciphertext produced by CryptoManager is the prologue
   * version. At present, this constant is both the version written and the
   * expected version. If a new version is introduced (e.g., to allow embedding
   * the HMAC key identifier and signature in a signed backup) the prologue
   * version will likely need to be configurable at the granularity of the
   * CryptoManager client (e.g., password encryption might use version 1, while
   * signed backups might use version 2.
   */
  private static final int CIPHERTEXT_PROLOGUE_VERSION = 1 ;
  // The map from encryption key ID to CipherKeyEntry (cache). The
  // cache is accessed by methods that request, publish, and import
  // keys.
  /**
   * The map from encryption key ID to CipherKeyEntry (cache). The cache is
   * accessed by methods that request, publish, and import keys.
   */
  private final Map<KeyEntryID, CipherKeyEntry> cipherKeyEntryCache
          = new ConcurrentHashMap<KeyEntryID, CipherKeyEntry>();
  // The map from encryption key ID to MacKeyEntry (cache). The cache
  // is accessed by methods that request, publish, and import keys.
  /**
   * The map from encryption key ID to MacKeyEntry (cache). The cache is
   * accessed by methods that request, publish, and import keys.
   */
  private final Map<KeyEntryID, MacKeyEntry> macKeyEntryCache
          = new ConcurrentHashMap<KeyEntryID, MacKeyEntry>();
  // The preferred key wrapping transformation
  /** The preferred key wrapping transformation. */
  private String preferredKeyWrappingTransformation;
  // TODO: Move the following configuration to backup or backend configuration.
  // TODO: https://opends.dev.java.net/issues/show_bug.cgi?id=2472
  // The preferred message digest algorithm for the Directory Server.
  /** The preferred message digest algorithm for the Directory Server. */
  private String preferredDigestAlgorithm;
  // The preferred cipher for the Directory Server.
  /** The preferred cipher for the Directory Server. */
  private String preferredCipherTransformation;
  // The preferred key length for the preferred cipher.
  /** The preferred key length for the preferred cipher. */
  private int preferredCipherTransformationKeyLengthBits;
  // The preferred MAC algorithm for the Directory Server.
  /** The preferred MAC algorithm for the Directory Server. */
  private String preferredMACAlgorithm;
  // The preferred key length for the preferred MAC algorithm.
  /** The preferred key length for the preferred MAC algorithm. */
  private int preferredMACAlgorithmKeyLengthBits;
  // TODO: Move the following configuration to replication configuration.
  // TODO: https://opends.dev.java.net/issues/show_bug.cgi?id=2473
  // The name of the local certificate to use for SSL.
  /** The name of the local certificate to use for SSL. */
  private final String sslCertNickname;
  // Whether replication sessions use SSL encryption.
  /** Whether replication sessions use SSL encryption. */
  private final boolean sslEncryption;
  // The set of SSL protocols enabled or null for the default set.
  /** The set of SSL protocols enabled or null for the default set. */
  private final SortedSet<String> sslProtocols;
  // The set of SSL cipher suites enabled or null for the default set.
  /** The set of SSL cipher suites enabled or null for the default set. */
  private final SortedSet<String> sslCipherSuites;
@@ -295,9 +298,8 @@
  }
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public boolean isConfigurationChangeAcceptable(
       CryptoManagerCfg cfg,
       List<Message> unacceptableReasons)
@@ -434,23 +436,18 @@
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
       CryptoManagerCfg cfg)
  /** {@inheritDoc} */
  @Override
  public ConfigChangeResult applyConfigurationChange(CryptoManagerCfg cfg)
  {
    ResultCode resultCode = ResultCode.SUCCESS;
    boolean adminActionRequired = false;
    List<Message> messages = new ArrayList<Message>();
    preferredDigestAlgorithm = cfg.getDigestAlgorithm();
    preferredMACAlgorithm = cfg.getMacAlgorithm();
    preferredMACAlgorithmKeyLengthBits = cfg.getMacKeyLength();
    preferredCipherTransformation = cfg.getCipherTransformation();
    preferredCipherTransformationKeyLengthBits = cfg.getCipherKeyLength();
    preferredKeyWrappingTransformation = cfg.getKeyWrappingTransformation();
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    return new ConfigChangeResult(ResultCode.SUCCESS, false,
        new ArrayList<Message>());
  }
@@ -506,9 +503,7 @@
            RDN.create(attrKeyID, distinguishedValue));
    // Construct the search filter.
    final String FILTER_OC_INSTANCE_KEY =
            new StringBuilder("(objectclass=")
                    .append(ocInstanceKey.getNameOrOID())
                    .append(")").toString();
        "(objectclass=" + ocInstanceKey.getNameOrOID() + ")";
    // Construct the attribute list.
    final LinkedHashSet<String> requestedAttributes
            = new LinkedHashSet<String>();
@@ -581,7 +576,7 @@
        ERR_CRYPTOMGR_FAILED_INSTANCE_CERTIFICATE_NULL.get(entryDN.toString());
        throw new CryptoManagerException(msg);
    }
    return(certificate);
    return certificate;
  }
@@ -656,9 +651,7 @@
         RDN.create(attrKeyID, distinguishedValue));
    // Construct the search filter.
    final String FILTER_OC_INSTANCE_KEY =
            new StringBuilder("(objectclass=")
                    .append(ocInstanceKey.getNameOrOID())
                    .append(")").toString();
        "(objectclass=" + ocInstanceKey.getNameOrOID() + ")";
    // Construct the attribute list.
    final LinkedHashSet<String> requestedAttributes
            = new LinkedHashSet<String>();
@@ -736,17 +729,12 @@
            = new HashMap<String, byte[]>();
    try {
      // Construct the search filter.
      final String FILTER_OC_INSTANCE_KEY
              = new StringBuilder("(objectclass=")
              .append(ocInstanceKey.getNameOrOID())
              .append(")").toString();
      final String FILTER_NOT_COMPROMISED = new StringBuilder("(!(")
              .append(attrCompromisedTime.getNameOrOID())
              .append("=*))").toString();
      final String searchFilter = new StringBuilder("(&")
              .append(FILTER_OC_INSTANCE_KEY)
              .append(FILTER_NOT_COMPROMISED)
              .append(")").toString();
      final String FILTER_OC_INSTANCE_KEY =
          "(objectclass=" + ocInstanceKey.getNameOrOID() + ")";
      final String FILTER_NOT_COMPROMISED =
          "(!(" + attrCompromisedTime.getNameOrOID() + "=*))";
      final String searchFilter =
          "(&" + FILTER_OC_INSTANCE_KEY + FILTER_NOT_COMPROMISED + ")";
      // Construct the attribute list.
      final LinkedHashSet<String> requestedAttributes
              = new LinkedHashSet<String>();
@@ -784,7 +772,7 @@
                    instanceKeysDN.toString(),
                    getExceptionMessage(ex)), ex);
    }
    return(certificateMap);
    return certificateMap;
  }
@@ -1061,7 +1049,9 @@
                  serversDN, SearchScope.SUBORDINATE_SUBTREE,
                  SearchFilter.createFilterFromString(filter));
        if (internalSearch.getResultCode() != ResultCode.SUCCESS)
        {
          continue;
        }
        LinkedList<SearchResultEntry> resultEntries =
             internalSearch.getSearchEntries();
@@ -1080,10 +1070,10 @@
          AtomicInteger nextMessageID = new AtomicInteger(1);
          LDAPConnectionOptions connectionOptions =
               new LDAPConnectionOptions();
          PrintStream nullPrintStream =
               new PrintStream(new OutputStream() {
                 public void write ( int b ) { }
               });
          PrintStream nullPrintStream = new PrintStream(new OutputStream() {
            @Override
            public void write(int b) {}
          });
          LDAPConnection connection =
               new LDAPConnection(hostname, ldapPort,
                                  connectionOptions,
@@ -1159,9 +1149,11 @@
  void importCipherKeyEntry(Entry entry)
       throws CryptoManagerException
  {
    // Ignore the entry if it does not have the appropriate
    // objectclass.
    if (!entry.hasObjectClass(ocCipherKey)) return;
    // Ignore the entry if it does not have the appropriate objectclass.
    if (!entry.hasObjectClass(ocCipherKey))
    {
      return;
    }
    try
    {
@@ -1194,7 +1186,10 @@
      for (String symmetricKey : symmetricKeys)
      {
        secretKey = decodeSymmetricKeyAttribute(symmetricKey);
        if (secretKey != null) break;
        if (secretKey != null)
        {
          break;
        }
      }
      if (null != secretKey) {
@@ -1263,9 +1258,11 @@
  void importMacKeyEntry(Entry entry)
       throws CryptoManagerException
  {
    // Ignore the entry if it does not have the appropriate
    // objectclass.
    if (!entry.hasObjectClass(ocMacKey)) return;
    // Ignore the entry if it does not have the appropriate objectclass.
    if (!entry.hasObjectClass(ocMacKey))
    {
      return;
    }
    try
    {
@@ -1295,7 +1292,10 @@
      for (String symmetricKey : symmetricKeys)
      {
        secretKey = decodeSymmetricKeyAttribute(symmetricKey);
        if (secretKey != null) break;
        if (secretKey != null)
        {
          break;
        }
      }
      if (secretKey == null)
@@ -1475,6 +1475,7 @@
     * @return {@code true} if the objects are the same, {@code false}
     * otherwise.
     */
    @Override
    public boolean equals(final Object obj){
      return obj instanceof KeyEntryID
              && fValue.equals(((KeyEntryID) obj).fValue);
@@ -1485,6 +1486,7 @@
     *
     * @return a hash code value for this {@code KeyEntryID}.
     */
    @Override
    public int hashCode() {
      return fValue.hashCode();
    }
@@ -1684,20 +1686,17 @@
            final String transformation,
            final int keyLengthBits)
    throws CryptoManagerException {
      final Map<KeyEntryID, CipherKeyEntry> cache
              = (null == cryptoManager)
              ? null : cryptoManager.cipherKeyEntryCache;
      final Map<KeyEntryID, CipherKeyEntry> cache =
          cryptoManager != null ? cryptoManager.cipherKeyEntryCache : null;
      CipherKeyEntry keyEntry = new CipherKeyEntry(transformation,
              keyLengthBits);
      // Validate the key entry. Record the initialization vector length, if
      // any.
      // Validate the key entry. Record the initialization vector length, if any
      final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
      // TODO: https://opends.dev.java.net/issues/show_bug.cgi?id=2471
      final byte[] iv = cipher.getIV();
      keyEntry.setIVLengthBits((null == iv) ? 0 : iv.length * Byte.SIZE);
      keyEntry.setIVLengthBits(null == iv ? 0 : iv.length * Byte.SIZE);
      if (null != cache) {
        /* The key is published to ADS before making it available in the local
@@ -1986,8 +1985,8 @@
     */
    private static String keyAlgorithmFromTransformation(
            String transformation){
    final int separatorIndex = transformation.indexOf('/');
      return (0 < separatorIndex)
      final int separatorIndex = transformation.indexOf('/');
      return 0 < separatorIndex
              ? transformation.substring(0, separatorIndex)
              : transformation;
    }
@@ -2239,8 +2238,8 @@
    throws CryptoManagerException {
      Validator.ensureNotNull(algorithm);
      final Map<KeyEntryID, MacKeyEntry> cache = (null == cryptoManager)
              ? null : cryptoManager.macKeyEntryCache;
      final Map<KeyEntryID, MacKeyEntry> cache =
          cryptoManager != null ? cryptoManager.macKeyEntryCache : null;
      final MacKeyEntry keyEntry = new MacKeyEntry(algorithm, keyLengthBits);
@@ -2619,6 +2618,7 @@
  /** {@inheritDoc} */
  @Override
  public String getPreferredMessageDigestAlgorithm()
  {
    return preferredDigestAlgorithm;
@@ -2626,6 +2626,7 @@
  /** {@inheritDoc} */
  @Override
  public MessageDigest getPreferredMessageDigest()
         throws NoSuchAlgorithmException
  {
@@ -2634,6 +2635,7 @@
  /** {@inheritDoc} */
  @Override
  public MessageDigest getMessageDigest(String digestAlgorithm)
         throws NoSuchAlgorithmException
  {
@@ -2642,6 +2644,7 @@
  /** {@inheritDoc} */
  @Override
  public byte[] digest(byte[] data)
         throws NoSuchAlgorithmException
  {
@@ -2651,6 +2654,7 @@
  /** {@inheritDoc} */
  @Override
  public byte[] digest(String digestAlgorithm, byte[] data)
         throws NoSuchAlgorithmException
  {
@@ -2659,6 +2663,7 @@
  /** {@inheritDoc} */
  @Override
  public byte[] digest(InputStream inputStream)
         throws IOException, NoSuchAlgorithmException
  {
@@ -2682,6 +2687,7 @@
  /** {@inheritDoc} */
  @Override
  public byte[] digest(String digestAlgorithm,
                       InputStream inputStream)
         throws IOException, NoSuchAlgorithmException
@@ -2705,6 +2711,7 @@
  /** {@inheritDoc} */
  @Override
  public String getMacEngineKeyEntryID()
          throws CryptoManagerException
  {
@@ -2714,6 +2721,7 @@
  /** {@inheritDoc} */
  @Override
  public String getMacEngineKeyEntryID(final String macAlgorithm,
                                       final int keyLengthBits)
         throws CryptoManagerException {
@@ -2731,16 +2739,18 @@
  /** {@inheritDoc} */
  @Override
  public Mac getMacEngine(String keyEntryID)
          throws CryptoManagerException
  {
    final MacKeyEntry keyEntry = MacKeyEntry.getKeyEntry(this,
            new KeyEntryID(keyEntryID));
    return (null == keyEntry) ? null : getMacEngine(keyEntry);
    return keyEntry != null ? getMacEngine(keyEntry) : null;
  }
  /** {@inheritDoc} */
  @Override
  public byte[] encrypt(byte[] data)
         throws GeneralSecurityException, CryptoManagerException
  {
@@ -2750,6 +2760,7 @@
  /** {@inheritDoc} */
  @Override
  public byte[] encrypt(String cipherTransformation,
                        int keyLengthBits,
                        byte[] data)
@@ -2769,7 +2780,7 @@
    final byte[] keyID = keyEntry.getKeyID().getByteValue();
    final byte[] iv = cipher.getIV();
    final int prologueLength
            = /* version */ 1 + keyID.length + ((null == iv) ? 0 : iv.length);
            = /* version */ 1 + keyID.length + (iv != null ? iv.length : 0);
    final int dataLength = cipher.getOutputSize(data.length);
    final byte[] cipherText = new byte[prologueLength + dataLength];
    int writeIndex = 0;
@@ -2787,6 +2798,7 @@
  /** {@inheritDoc} */
  @Override
  public CipherOutputStream getCipherOutputStream(
          OutputStream outputStream) throws CryptoManagerException
  {
@@ -2796,6 +2808,7 @@
  /** {@inheritDoc} */
  @Override
  public CipherOutputStream getCipherOutputStream(
          String cipherTransformation, int keyLengthBits,
          OutputStream outputStream)
@@ -2833,6 +2846,7 @@
  /** {@inheritDoc} */
  @Override
  public byte[] decrypt(byte[] data)
         throws GeneralSecurityException,
                CryptoManagerException
@@ -2905,9 +2919,12 @@
    final Cipher cipher = getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
    if(data.length - readIndex > 0)
          return cipher.doFinal(data, readIndex, data.length - readIndex);
    else {
      //IBM Java 6 throws an IllegalArgumentException when there's n
    {
      return cipher.doFinal(data, readIndex, data.length - readIndex);
    }
    else
    {
      // IBM Java 6 throws an IllegalArgumentException when there's no
      // data to process.
      return cipher.doFinal();
    }
@@ -2915,6 +2932,7 @@
 /** {@inheritDoc} */
  @Override
  public CipherInputStream getCipherInputStream(
          InputStream inputStream) throws CryptoManagerException
  {
@@ -2972,6 +2990,7 @@
  /** {@inheritDoc} */
  @Override
  public int compress(byte[] src, int srcOff, int srcLen,
                      byte[] dst, int dstOff, int dstLen)
  {
@@ -2999,6 +3018,7 @@
  /** {@inheritDoc} */
  @Override
  public int uncompress(byte[] src, int srcOff, int srcLen,
                        byte[] dst, int dstOff, int dstLen)
         throws DataFormatException
@@ -3033,6 +3053,7 @@
  /** {@inheritDoc} */
  @Override
  public SSLContext getSslContext(String sslCertNickname)
       throws ConfigException
  {
@@ -3077,24 +3098,28 @@
  /** {@inheritDoc} */
  @Override
  public String getSslCertNickname()
  {
    return sslCertNickname;
  }
  /** {@inheritDoc} */
  @Override
  public boolean isSslEncryption()
  {
    return sslEncryption;
  }
  /** {@inheritDoc} */
  @Override
  public SortedSet<String> getSslProtocols()
  {
    return sslProtocols;
  }
  /** {@inheritDoc} */
  @Override
  public SortedSet<String> getSslCipherSuites()
  {
    return sslCipherSuites;
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -214,7 +214,7 @@
  private boolean solveConflictFlag = true;
  private final InternalClientConnection conn = getRootConnection();
  private volatile boolean shutdown = false;
  private final AtomicBoolean shutdown = new AtomicBoolean();
  private volatile boolean disabled = false;
  private volatile boolean stateSavingDisabled = false;
@@ -258,7 +258,7 @@
   */
  /** Holds the fractional configuration for this domain, if any. */
  private FractionalConfig fractionalConfig;
  private final FractionalConfig fractionalConfig;
  /**
   * The list of attributes that cannot be used in fractional replication
@@ -374,11 +374,6 @@
  private class RSUpdater extends DirectoryThread
  {
    private final CSN startCSN;
    /**
     * Used to communicate that the current thread computation needs to
     * shutdown.
     */
    private AtomicBoolean shutdown = new AtomicBoolean(false);
    protected RSUpdater(CSN replServerMaxCSN)
    {
@@ -392,8 +387,8 @@
    @Override
    public void run()
    {
      // Replication server is missing some of our changes: let's
      // send them to him.
      // Replication server is missing some of our changes:
      // let's send them to him.
      logError(DEBUG_GOING_TO_SEARCH_FOR_CHANGES.get());
      /*
@@ -402,7 +397,7 @@
       */
      try
      {
        if (buildAndPublishMissingChanges(startCSN, broker, shutdown))
        if (buildAndPublishMissingChanges(startCSN, broker))
        {
          logError(DEBUG_CHANGES_SENT.get());
          synchronized(replayOperations)
@@ -421,7 +416,8 @@
           */
          logError(ERR_CANNOT_RECOVER_CHANGES.get(getBaseDNString()));
        }
      } catch (Exception e)
      }
      catch (Exception e)
      {
        /*
         * An error happened trying to search for the updates
@@ -440,17 +436,8 @@
        rsUpdater.compareAndSet(this, null);
      }
    }
    /** {@inheritDoc} */
    @Override
    public void initiateShutdown()
    {
      this.shutdown.set(true);
      super.initiateShutdown();
    }
  }
  /**
   * Creates a new ReplicationDomain using configuration from configEntry.
   *
@@ -825,7 +812,7 @@
   */
  static class AttributeValueStringIterator implements Iterator<String>
  {
    private Iterator<AttributeValue> attrValIt;
    private final Iterator<AttributeValue> attrValIt;
    /**
     * Creates a new AttributeValueStringIterator object.
@@ -2268,9 +2255,8 @@
   */
  public void shutdown()
  {
    if (!shutdown)
    if (shutdown.compareAndSet(false, true))
    {
      shutdown = true;
      final RSUpdater rsUpdater = this.rsUpdater.get();
      if (rsUpdater != null)
      {
@@ -2293,7 +2279,7 @@
      disableService();
    }
    // wait for completion of the persistentServerState thread.
    // wait for completion of the ServerStateFlush thread.
    try
    {
      while (!done)
@@ -4160,14 +4146,12 @@
   *          The CSN where we need to start the search
   * @param session
   *          The session to use to publish the changes
   * @param shutdown
   *          whether the current run must be stopped
   * @return A boolean indicating he success of the operation.
   * @throws Exception
   *           if an Exception happens during the search.
   */
  boolean buildAndPublishMissingChanges(CSN startCSN,
      ReplicationBroker session, AtomicBoolean shutdown) throws Exception
  boolean buildAndPublishMissingChanges(CSN startCSN, ReplicationBroker session)
      throws Exception
  {
    // Trim the changes in replayOperations that are older than the startCSN.
    synchronized (replayOperations)
@@ -4681,7 +4665,7 @@
    /**
     * Base DN the fractional configuration is for.
     */
    private DN baseDN;
    private final DN baseDN;
    /**
     * Constructs a new fractional configuration object.
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
@@ -26,13 +26,10 @@
 */
package org.opends.server.replication.plugin;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn.AssuredType;
import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn.IsolationPolicy;
import org.opends.server.admin.std.server.ExternalChangelogDomainCfg;
@@ -47,9 +44,9 @@
 */
public class DomainFakeCfg implements ReplicationDomainCfg
{
  private DN baseDN;
  private int serverId;
  private SortedSet<String> replicationServers;
  private final DN baseDN;
  private final int serverId;
  private final SortedSet<String> replicationServers;
  private long heartbeatInterval = 1000;
  /**
@@ -67,12 +64,12 @@
  /** Timeout (in milliseconds) when waiting for acknowledgments */
  private long assuredTimeout = 1000;
  /** Group id */
  private int groupId = 1;
  private final int groupId;
  /** Referrals urls to be published to other servers of the topology */
  private SortedSet<String> refUrls = new TreeSet<String>();
  private SortedSet<String> fractionalExcludes = new TreeSet<String>();
  private SortedSet<String> fractionalIncludes = new TreeSet<String>();
  private final SortedSet<String> fractionalExcludes = new TreeSet<String>();
  private final SortedSet<String> fractionalIncludes = new TreeSet<String>();
  private ExternalChangelogDomainCfg eclCfg =
    new ExternalChangelogDomainFakeCfg(true, null, null);
@@ -84,33 +81,7 @@
   */
  public DomainFakeCfg(DN baseDN, int serverId, SortedSet<String> replServers)
  {
    this.baseDN = baseDN;
    this.serverId = serverId;
    this.replicationServers = replServers;
  }
  /**
   * Creates a new Domain with the provided information
   * (with some fractional configuration provided)
   */
  public DomainFakeCfg(DN baseDN, int serverId, SortedSet<String> replServers,
    List<String> fractionalExcludes, List<String> fractionalIncludes)
  {
    this(baseDN, serverId, replServers);
    if (fractionalExcludes != null)
    {
      for (String str : fractionalExcludes)
      {
        this.fractionalExcludes.add(str);
      }
    }
    if (fractionalIncludes != null)
    {
      for (String str : fractionalIncludes)
      {
        this.fractionalIncludes.add(str);
      }
    }
    this(baseDN, serverId, replServers, -1);
  }
  /**
@@ -120,7 +91,9 @@
  public DomainFakeCfg(DN baseDN, int serverId, SortedSet<String> replServers,
    int groupId)
  {
    this(baseDN, serverId, replServers);
    this.baseDN = baseDN;
    this.serverId = serverId;
    this.replicationServers = replServers;
    this.groupId = groupId;
  }
@@ -132,7 +105,7 @@
    AssuredType assuredType, int assuredSdLevel, int groupId,
    long assuredTimeout, SortedSet<String> refUrls)
  {
    this(baseDN, serverId, replServers);
    this(baseDN, serverId, replServers, groupId);
    switch(assuredType)
    {
      case NOT_ASSURED:
@@ -143,31 +116,12 @@
        break;
    }
    this.assuredSdLevel = assuredSdLevel;
    this.groupId = groupId;
    this.assuredTimeout = assuredTimeout;
    if (refUrls != null)
      this.refUrls = refUrls;
  }
  /**
   * Create a new Domain from the provided arguments.
   *
   * @param baseDN         The baseDN in string form.
   * @param serverId       The serverID.
   * @param replServer     The replication Server that will be used.
   *
   * @throws DirectoryException  When the provided string is not a valid DN.
   */
  public DomainFakeCfg(String baseDN, int serverId, String replServer)
         throws DirectoryException
  {
    this.replicationServers = new TreeSet<String>();
    this.replicationServers.add(replServer);
    this.baseDN = DN.decode(baseDN);
    this.serverId = serverId;
  }
  /**
   * {@inheritDoc}
   */
  @Override
@@ -379,54 +333,6 @@
  throws ConfigException
  { this.eclCfg=eclCfg;}
  /**
   * Registers to be notified when the ECL Domain is added.
   *
   * @param listener
   *          The ECL Domain configuration add listener.
   * @throws ConfigException
   *          If the add listener could not be registered.
   */
  public void addECLDomainAddListener(
      ConfigurationAddListener<ExternalChangelogDomainCfg> listener)
      throws ConfigException
  {}
  /**
   * Deregisters an existing ECL Domain configuration add listener.
   *
   * @param listener
   *          The ECL Domain configuration add listener.
   */
  public void removeECLDomainAddListener(
      ConfigurationAddListener<ExternalChangelogDomainCfg> listener)
  {}
  /**
   * Registers to be notified the ECL Domain is deleted.
   *
   * @param listener
   *          The ECL Domain configuration delete listener.
   * @throws ConfigException
   *          If the delete listener could not be registered.
   */
  public void addECLDomainDeleteListener(
      ConfigurationDeleteListener<ExternalChangelogDomainCfg> listener)
      throws ConfigException
  {}
  /**
   * Deregisters an existing ECL Domain configuration delete listener.
   *
   * @param listener
   *          The ECL Domain configuration delete listener.
   */
  public void removeECLDomainDeleteListener(
      ConfigurationDeleteListener<ExternalChangelogDomainCfg> listener)
  {}
  @Override
  public boolean isLogChangenumber()
  {
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/HistoricalCsnOrderingTest.java
@@ -30,7 +30,6 @@
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import org.assertj.core.api.Assertions;
import org.opends.messages.Category;
@@ -62,13 +61,13 @@
{
  private final int serverId = 123;
  private SortedSet<String> replServers = new TreeSet<String>();
  private final SortedSet<String> replServers = new TreeSet<String>();
  public static class TestBroker extends ReplicationBroker
  private static class TestBroker extends ReplicationBroker
  {
    private List<ReplicationMsg> list;
    private final List<ReplicationMsg> list;
    public TestBroker(List<ReplicationMsg> list)
    private TestBroker(List<ReplicationMsg> list)
    {
      super(new DummyReplicationDomain(0), null,
          new DomainFakeCfg(null, 0, TestCaseUtils.<String> newSortedSet()), null);
@@ -182,7 +181,7 @@
    TestBroker session = new TestBroker(opList);
      CSN csn = new CSN(startTime, 0, serverId);
      boolean result = rd1.buildAndPublishMissingChanges(csn, session, new AtomicBoolean());
      boolean result = rd1.buildAndPublishMissingChanges(csn, session);
    assertTrue(result, "buildAndPublishMissingChanges has failed");
    assertEquals(opList.size(), 3, "buildAndPublishMissingChanges should return 3 operations");
    assertTrue(opList.getFirst().getClass().equals(AddMsg.class));
@@ -196,7 +195,7 @@
    opList = new LinkedList<ReplicationMsg>();
    session = new TestBroker(opList);
      result = rd1.buildAndPublishMissingChanges(fromCSN, session, new AtomicBoolean());
      result = rd1.buildAndPublishMissingChanges(fromCSN, session);
    assertTrue(result, "buildAndPublishMissingChanges has failed");
    assertEquals(opList.size(), 1, "buildAndPublishMissingChanges should return 1 operation");
    assertTrue(opList.getFirst().getClass().equals(ModifyMsg.class));
@@ -283,7 +282,7 @@
      // Call the buildAndPublishMissingChanges and check that this method
      // correctly generates the 4 operations in the correct order.
      CSN csn = new CSN(startTime, 0, serverId);
      boolean result = rd1.buildAndPublishMissingChanges(csn, session, new AtomicBoolean());
      boolean result = rd1.buildAndPublishMissingChanges(csn, session);
    assertTrue(result, "buildAndPublishMissingChanges has failed");
    assertEquals(opList.size(), 5, "buildAndPublishMissingChanges should return 5 operations");
    ReplicationMsg msg = opList.removeFirst();
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java
@@ -112,8 +112,8 @@
  /** The LDAPStatistics object associated with the LDAP connection handler. */
  private LDAPStatistics ldapStatistics;
  private int brokerSessionTimeout = 5000;
  private int maxWindow = 100;
  private final int brokerSessionTimeout = 5000;
  private final int maxWindow = 100;
  /**
   * When used in a search operation, it includes all attributes (user and
@@ -1109,17 +1109,19 @@
        } else if (i==2)
        {
          checkValue(resultEntry, "changetype", "add");
          String expectedValue1 = "objectClass: domain\nobjectClass: top\n"
              + "entryUUID: 11111111-1111-1111-1111-111111111111\n";
          checkValue(resultEntry, "changes", expectedValue1);
          checkLDIF(resultEntry, "changes",
              "objectClass: domain",
              "objectClass: top",
              "entryUUID: 11111111-1111-1111-1111-111111111111");
          checkValue(resultEntry,"targetentryuuid",user1entryUUID);
        } else if (i==3)
        {
          // check the MOD entry has the right content
          checkValue(resultEntry, "changetype", "modify");
          String expectedValue =
              "replace: description\n" + "description: new value\n-\n";
          checkValue(resultEntry,"changes",expectedValue);
          checkLDIF(resultEntry, "changes",
              "replace: description",
              "description: new value",
              "-");
          checkValue(resultEntry,"targetentryuuid",tn+"uuid3");
        } else if (i==4)
        {
@@ -1225,11 +1227,64 @@
  private static void checkValue(Entry entry, String attrName, String expectedValue)
  {
    assertThat(getAttributeValue(entry, attrName))
    assertFalse(expectedValue.contains("\n"),
        "Use checkLDIF() method for asserting on value: \"" + expectedValue + "\"");
    final String actualValue = getAttributeValue(entry, attrName);
    assertThat(actualValue)
        .as("In entry " + entry + " incorrect value for attr '" + attrName + "'")
        .isEqualToIgnoringCase(expectedValue);
  }
  /**
   * Asserts the attribute value as LDIF to ignore lines ordering.
   */
  private static void checkLDIF(Entry entry, String attrName, String... expectedLDIFLines)
  {
    final String actualVal = getAttributeValue(entry, attrName);
    final Set<Set<String>> actual = toLDIFEntries(actualVal.split("\n"));
    final Set<Set<String>> expected = toLDIFEntries(expectedLDIFLines);
    assertThat(actual)
        .as("In entry " + entry + " incorrect value for attr '" + attrName + "'")
        .isEqualTo(expected);
  }
  /**
   * Returns a data structure allowing to compare arbitrary LDIF lines. The
   * algorithm splits LDIF entries on lines containing only a dash ("-"). It
   * then returns LDIF entries and lines in an LDIF entry in ordering
   * insensitive data structures.
   * <p>
   * Note: a last line with only a dash ("-") is significant. i.e.:
   *
   * <pre>
   * <code>
   * boolean b = toLDIFEntries("-").equals(toLDIFEntries()));
   * System.out.println(b); // prints "false"
   * </code>
   * </pre>
   */
  private static Set<Set<String>> toLDIFEntries(String... ldifLines)
  {
    final Set<Set<String>> results = new HashSet<Set<String>>();
    Set<String> ldifEntryLines = new HashSet<String>();
    for (String ldifLine : ldifLines)
    {
      if (!"-".equals(ldifLine))
      {
        // same entry keep adding
        ldifEntryLines.add(ldifLine);
      }
      else
      {
        // this is a new entry
        results.add(ldifEntryLines);
        ldifEntryLines = new HashSet<String>();
      }
    }
    results.add(ldifEntryLines);
    return results;
  }
  private static String getAttributeValue(Entry entry, String attrName)
  {
    List<Attribute> attrs = entry.getAttribute(attrName.toLowerCase());
@@ -1245,15 +1300,17 @@
  private static void checkValues(Entry entry, String attrName,
      Set<String> expectedValues)
  {
    final Set<String> values = new HashSet<String>();
    for (Attribute a : entry.getAttribute(attrName))
    {
      for (AttributeValue av : a)
      {
        assertThat(expectedValues)
            .as("In entry " + entry + " incorrect value for attr '" + attrName + "'")
            .contains(av.toString());
        values.add(av.toString());
      }
    }
    assertThat(values)
      .as("In entry " + entry + " incorrect values for attr '" + attrName + "'")
      .isEqualTo(expectedValues);
  }
  /**
@@ -2226,16 +2283,19 @@
    final SearchResultEntry addEntry = entries.get(++i);
    checkValue(addEntry, "changetype", "add");
    commonAssert(addEntry, user1entryUUID, firstChangeNumber, i, tn, csns[i]);
    String expectedValue1 = "objectClass: domain\nobjectClass: top\n" + "entryUUID: "
        + user1entryUUID + "\n";
    checkValue(addEntry, "changes", expectedValue1);
    checkLDIF(addEntry, "changes",
        "objectClass: domain",
        "objectClass: top",
        "entryUUID: " + user1entryUUID);
    // check the MOD entry has the right content
    final SearchResultEntry modEntry = entries.get(++i);
    checkValue(modEntry, "changetype", "modify");
    commonAssert(modEntry, user1entryUUID, firstChangeNumber, i, tn, csns[i]);
    final String expectedValue = "replace: description\n" + "description: new value\n-\n";
    checkValue(modEntry, "changes", expectedValue);
    checkLDIF(modEntry, "changes",
        "replace: description",
        "description: new value",
        "-");
    // check the MODDN entry has the right content
    final SearchResultEntry moddnEntry = entries.get(++i);