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

Jean-Noel Rouvignac
11.17.2014 bdcbee41067e1f439a8ebe7fd0a9dd01799b593a
Code cleanups.



MessageHandler.java:
Extracted method fillLateQueue().

MsgQueue.java:
Added toString().

PersistentServerState.java:
Renamed field baseDn => baseDN.
Made updateStateEntry() return a boolean instead of a ResultCode.
Used Collections.singleton*() methods.
Added toString().



JEReplicaDBCursor.java:
In next(), factorized code.

ExternalChangeLogTest.java:
Inlined a few passthrough test methods.
Renamed createControls() to createCookieControl().
In getControls(), removed an esoteric way to call Assert.fail(). Let the exception bubble up dammit!

InternalSearchMonitorTestCase.java:
Simplified code calling processSearch().
In getMonitorNames(), do not copy the monitor names.


BrowserController.java:
Reduce fields + methods visibilities.
Added final keyword to fields.
Removed unused methods removeSuffix(), showAttributeName(), removeBrowserEventListener(), notifyChildEntryChanged(), notifyChildEntryAdded(), notifyChildEntryDeleted(), startRefresh(), shutDown(), getAttrsForGreenSearch(), entryArrayFromCollection(), nodeArrayFromCollection().
Extracted methods getAciCount(), getNewIcon() and toInt().
Used early returns.

IconPool.java:
Removed unused method maskedIcon().

LDAPConnectionPool.java:
Reduce fields + methods visibilities.
Added final keyword to fields.
Used static import for ConnectionUtils.
Changed makeLDAPUrl() to accept a single InitialLdapContext parameter.
Removed unused methods removeReferralAuthenticationListener(), flush(), getAuthDN(), getAuthPassword(), getRegisteredAuthentication().
8 files modified
851 ■■■■■ changed files
opendj3-server-dev/src/guitools/org/opends/guitools/controlpanel/browser/IconPool.java 116 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/guitools/org/opends/guitools/controlpanel/browser/LDAPConnectionPool.java 196 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/replication/plugin/PersistentServerState.java 72 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/replication/server/MessageHandler.java 59 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/replication/server/MsgQueue.java 54 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/je/JEReplicaDBCursor.java 19 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java 105 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java 230 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/guitools/org/opends/guitools/controlpanel/browser/IconPool.java
@@ -22,20 +22,10 @@
 *
 *
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2013 ForgeRock AS.
 *      Portions Copyright 2013-2014 ForgeRock AS.
 */
package org.opends.guitools.controlpanel.browser;
import static org.opends.messages.AdminToolMessages.*;
import java.awt.Canvas;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;
import java.util.HashMap;
import java.util.Set;
import java.util.SortedSet;
@@ -46,6 +36,8 @@
import org.opends.quicksetup.ui.UIFactory;
import org.opends.server.util.ServerConstants;
import static org.opends.messages.AdminToolMessages.*;
/**
 * This class is used as a cache containing the icons that are used by the
 * BrowserController to update the nodes.  It keeps some icons associated with
@@ -66,10 +58,11 @@
   */
  public static final int MODIFIER_ERROR    = 0x04;
  private HashMap<String, ImageIcon> iconTable =
  private final HashMap<String, ImageIcon> iconTable =
    new HashMap<String, ImageIcon>();
  private HashMap<String, String> pathTable = new HashMap<String, String>();
  private HashMap<String, String> descriptionTable =
  private final HashMap<String, String> pathTable =
      new HashMap<String, String>();
  private final HashMap<String, String> descriptionTable =
    new HashMap<String, String>();
  private ImageIcon defaultLeafIcon;
  private ImageIcon suffixIcon;
@@ -117,12 +110,9 @@
    "passwordpolicy", INFO_PASSWORD_POLICY_ICON_DESCRIPTION.get().toString()
  };
  private String GENERIC_OBJECT_DESCRIPTION = "Generic entry";
  private final String GENERIC_OBJECT_DESCRIPTION = "Generic entry";
  /**
   * The default constructor.
   *
   */
  /** The default constructor. */
  public IconPool() {
    // Recopy ICON_PATH in pathTable for fast access
    for (int i = 0; i < ICON_PATH.length; i = i+2) {
@@ -144,15 +134,12 @@
   * modifiers.
   */
  public ImageIcon getIcon(SortedSet<String> objectClasses, int modifiers) {
    ImageIcon result;
    String key = makeKey(objectClasses, modifiers);
    result = iconTable.get(key);
    ImageIcon result = iconTable.get(key);
    if (result == null) {
      result = makeIcon(objectClasses, modifiers);
      iconTable.put(key, result);
    }
    return result;
  }
@@ -270,7 +257,7 @@
    ImageIcon result;
    // Find the icon associated to the object class
    if ((objectClasses == null) || (objectClasses.size() == 0)) {
    if (objectClasses == null || objectClasses.size() == 0) {
      result = getDefaultContainerIcon();
    }
    else {
@@ -328,87 +315,8 @@
    if(ocValues != null) {
      result.append(Utilities.getStringFromCollection(ocValues, ""));
    }
    result.append(String.valueOf(modifiers));
    result.append(modifiers);
    return result.toString();
  }
    /**
     * Returns a RemoteImage corresponding to the superposition of the icon
     * Image and the mask Image.
     *
     * @param icon the RemoteImage that we want to bar.
     * @param mask the ImageIcond to be used as mask.
     * @return a RemoteImage corresponding to the superposition of the icon
     * Image and the mask Image.
     */
  public static ImageIcon maskedIcon(ImageIcon icon, ImageIcon mask) {
    ImageIcon fReturn;
    int TRANSPARENT = 16711165;  // The value of a transparent pixel
    int h = icon.getIconHeight();
    int w = icon.getIconWidth();
    if (mask.getImageLoadStatus() != MediaTracker.COMPLETE) {
      return null;
    }
    Image maskImage = mask.getImage();
    Image scaledMaskImage = maskImage.getScaledInstance(w, h ,
        Image.SCALE_SMOOTH);
    ImageIcon scaledMask = new ImageIcon(scaledMaskImage);
    if (scaledMask.getImageLoadStatus() != MediaTracker.COMPLETE) {
      return null;
    }
    int[] iconPixels = new int[w * h];
    try {
      PixelGrabber pg =
        new PixelGrabber(icon.getImage(), 0, 0, w, h, iconPixels, 0, w);
      pg.grabPixels();
      if ((pg.status() & ImageObserver.ABORT) !=0) {
        return null;
      }
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
    int[] filterPixels = new int[w * h];
    try {
      PixelGrabber pgf =
        new PixelGrabber(scaledMask.getImage(), 0, 0, w, h, filterPixels, 0, w);
      pgf.grabPixels();
      if ((pgf.status() & ImageObserver.ABORT) !=0) {
        fReturn = null;
        return fReturn;
      }
    } catch (Exception e) {
      e.printStackTrace();
      fReturn = null;
      return fReturn;
    }
    int[] newPixels = new int[w * h];
    for( int i = 0; i < h; i++)
      for (int j = 0; j < w; j++)
        if (filterPixels[j + i*w] != TRANSPARENT) {
          newPixels[j + i*w] = filterPixels[j + i*w];
        } else {
          newPixels[j + i*w] = iconPixels[j + i*w];
        }
    Canvas component = new Canvas();
    Image newImage = component.getToolkit().createImage(
        new MemoryImageSource(
            w, h, ColorModel.getRGBdefault(), newPixels, 0, w));
    fReturn = new ImageIcon(newImage, icon.getDescription());
    return fReturn;
  }
}
opendj3-server-dev/src/guitools/org/opends/guitools/controlpanel/browser/LDAPConnectionPool.java
@@ -27,7 +27,6 @@
package org.opends.guitools.controlpanel.browser;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import javax.naming.NamingException;
@@ -44,6 +43,8 @@
import com.forgerock.opendj.cli.CliConstants;
import static org.opends.admin.ads.util.ConnectionUtils.*;
/**
 * An LDAPConnectionPool is a pool of LDAPConnection.
 * <BR><BR>
@@ -73,11 +74,12 @@
 */
public class LDAPConnectionPool {
  HashMap<String, AuthRecord> authTable = new HashMap<String, AuthRecord>();
  HashMap<String, ConnectionRecord> connectionTable =
  private final HashMap<String, AuthRecord> authTable =
      new HashMap<String, AuthRecord>();
  private final HashMap<String, ConnectionRecord> connectionTable =
    new HashMap<String, ConnectionRecord>();
  ArrayList<ReferralAuthenticationListener> listeners;
  private ArrayList<ReferralAuthenticationListener> listeners;
  private Control[] requestControls = new Control[] {};
  private ApplicationTrustManager trustManager;
@@ -91,28 +93,20 @@
   * connection pool, <CODE>false</CODE> otherwise.
   */
  public boolean isConnectionRegistered(InitialLdapContext ctx) {
    boolean isConnectionRegistered = false;
    for (String key : connectionTable.keySet())
    {
      ConnectionRecord cr = connectionTable.get(key);
      if (cr.ctx != null) {
        isConnectionRegistered =
         ConnectionUtils.getHostName(cr.ctx).equals(
             ConnectionUtils.getHostName(ctx)) &&
        (ConnectionUtils.getPort(cr.ctx) == ConnectionUtils.getPort(ctx)) &&
        ConnectionUtils.getBindDN(cr.ctx).equals(
            ConnectionUtils.getBindDN(ctx)) &&
        ConnectionUtils.getBindPassword(cr.ctx).equals(
            ConnectionUtils.getBindPassword(ctx)) &&
        (ConnectionUtils.isSSL(cr.ctx) == ConnectionUtils.isSSL(ctx)) &&
        (ConnectionUtils.isStartTLS(cr.ctx) == ConnectionUtils.isStartTLS(ctx));
      }
      if (isConnectionRegistered)
      {
        break;
      if (cr.ctx != null
          && getHostName(cr.ctx).equals(getHostName(ctx))
          && getPort(cr.ctx) == getPort(ctx)
          && getBindDN(cr.ctx).equals(getBindDN(ctx))
          && getBindPassword(cr.ctx).equals(getBindPassword(ctx))
          && isSSL(cr.ctx) == isSSL(ctx)
          && isStartTLS(cr.ctx) == isStartTLS(ctx)) {
        return true;
      }
    }
    return isConnectionRegistered;
    return false;
  }
  /**
@@ -121,12 +115,7 @@
   */
  public void registerConnection(InitialLdapContext ctx) {
    registerAuth(ctx);
    LDAPURL url = makeLDAPUrl(
                  ConnectionUtils.getHostName(ctx),
                  ConnectionUtils.getPort(ctx),
                  "",
                  ConnectionUtils.isSSL(ctx)
                  );
    LDAPURL url = makeLDAPUrl(ctx);
    String key = makeKeyFromLDAPUrl(url);
    ConnectionRecord cr = new ConnectionRecord();
    cr.ctx = ctx;
@@ -143,11 +132,7 @@
  public void unregisterConnection(InitialLdapContext ctx)
  throws NamingException
  {
    LDAPURL url = makeLDAPUrl(
        ConnectionUtils.getHostName(ctx),
        ConnectionUtils.getPort(ctx),
        "",
        ConnectionUtils.isSSL(ctx));
    LDAPURL url = makeLDAPUrl(ctx);
    unRegisterAuth(url);
    String key = makeKeyFromLDAPUrl(url);
    connectionTable.remove(key);
@@ -166,17 +151,6 @@
  }
  /**
   * Removes a referral authentication listener.
   * @param listener the referral authentication listener.
   */
  public void removeReferralAuthenticationListener(
      ReferralAuthenticationListener listener) {
    if (listeners != null) {
      listeners.remove(listener);
    }
  }
  /**
   * Returns an LDAPConnection for accessing the specified url.
   * If no connection are available for the protocol/host/port
   * of the URL, getConnection() makes a new one and call connect().
@@ -289,35 +263,17 @@
    if (targetRecord == null) { // ldc is not in _connectionTable -> bug
      throw new IllegalArgumentException("Invalid LDAP connection");
    }
    else {
      synchronized(targetRecord) {
        targetRecord.counter--;
        if ((targetRecord.counter == 0) && targetRecord.disconnectAfterUse) {
          disconnectAndRemove(targetRecord);
        }
      }
    }
  }
  /**
   * Disconnect the connections which are not being used.
   * Connections being used will be disconnected as soon
   * as they are released.
   */
  public synchronized void flush() {
    for (ConnectionRecord cr : connectionTable.values())
    synchronized (targetRecord)
    {
      if (cr.counter <= 0) {
        disconnectAndRemove(cr);
      }
      else {
        cr.disconnectAfterUse = true;
      targetRecord.counter--;
      if (targetRecord.counter == 0 && targetRecord.disconnectAfterUse)
      {
        disconnectAndRemove(targetRecord);
      }
    }
  }
  /**
   * Register authentication data.
   * If authentication data are already available for the protocol/host/port
@@ -333,14 +289,11 @@
   * provided authentication (for testing purposes).
   * @throws NamingException if an error occurs connecting.
   */
  public void registerAuth(LDAPURL ldapUrl, String dn, String pw,
      boolean connect)
  throws NamingException {
  private void registerAuth(LDAPURL ldapUrl, String dn, String pw,
      boolean connect) throws NamingException {
    String key = makeKeyFromLDAPUrl(ldapUrl);
    AuthRecord ar;
    ar = new AuthRecord();
    ar.ldapUrl  = ldapUrl;
    final AuthRecord ar = new AuthRecord();
    ar.dn       = dn;
    ar.password = pw;
@@ -373,15 +326,10 @@
   * @param ctx the connection that we retrieve the authentication information
   * from.
   */
  public void registerAuth(InitialLdapContext ctx) {
    LDAPURL url = makeLDAPUrl(
      ConnectionUtils.getHostName(ctx),
      ConnectionUtils.getPort(ctx),
      "",
      ConnectionUtils.isSSL(ctx));
  private void registerAuth(InitialLdapContext ctx) {
    LDAPURL url = makeLDAPUrl(ctx);
    try {
      registerAuth(url, ConnectionUtils.getBindDN(ctx),
          ConnectionUtils.getBindPassword(ctx), false);
      registerAuth(url, getBindDN(ctx), getBindPassword(ctx), false);
    }
    catch (NamingException x) {
      throw new RuntimeException("Bug");
@@ -397,7 +345,7 @@
   * unregistered.
   * @throws NamingException if the unbind fails.
   */
  public void unRegisterAuth(LDAPURL ldapUrl) throws NamingException {
  private void unRegisterAuth(LDAPURL ldapUrl) throws NamingException {
    String key = makeKeyFromLDAPUrl(ldapUrl);
    authTable.remove(key);
@@ -405,45 +353,6 @@
  }
  /**
   * Get authentication DN registered for this url.
   * @param ldapUrl the LDAP URL for which we want to get authentication DN.
   * @return the bind DN of the authentication.
   */
  public synchronized String getAuthDN(LDAPURL ldapUrl) {
    String result;
    String key = makeKeyFromLDAPUrl(ldapUrl);
    AuthRecord ar = authTable.get(key);
    if (ar == null) {
      result = null;
    }
    else {
      result = ar.dn;
    }
    return result;
  }
  /**
   * Get authentication password registered for this url.
   * @param ldapUrl the LDAP URL for which we want to get authentication
   * password.
   * @return the password of the authentication.
   */
  public synchronized String getAuthPassword(LDAPURL ldapUrl) {
    String result;
    String key = makeKeyFromLDAPUrl(ldapUrl);
    AuthRecord ar = authTable.get(key);
    if (ar == null) {
      result = null;
    }
    else {
      result = ar.password;
    }
    return result;
  }
  /**
   * Disconnect the connection associated to a record
   * and remove the record from connectionTable.
   * @param cr the ConnectionRecord to remove.
@@ -492,8 +401,7 @@
   */
  private static String makeKeyFromRecord(ConnectionRecord rec) {
    String protocol = ConnectionUtils.isSSL(rec.ctx) ? "LDAPS" : "LDAP";
    return protocol + ":" + ConnectionUtils.getHostName(rec.ctx) + ":" +
    ConnectionUtils.getPort(rec.ctx);
    return protocol + ":" + getHostName(rec.ctx) + ":" + getPort(rec.ctx);
  }
  /**
@@ -507,24 +415,18 @@
  private InitialLdapContext createLDAPConnection(LDAPURL ldapUrl,
      AuthRecord ar) throws NamingException
  {
    InitialLdapContext ctx;
    // Take the base DN out of the URL and only keep the protocol, host and port
    ldapUrl = new LDAPURL(ldapUrl.getScheme(), ldapUrl.getHost(),
          ldapUrl.getPort(), (DN)null, null, null, null, null);
    if (isSecureLDAPUrl(ldapUrl))
    {
      ctx = ConnectionUtils.createLdapsContext(ldapUrl.toString(), ar.dn,
      return ConnectionUtils.createLdapsContext(ldapUrl.toString(), ar.dn,
          ar.password, getConnectTimeout(), null,
          getTrustManager() , getKeyManager());
          getTrustManager(), getKeyManager());
    }
    else
    {
      ctx = ConnectionUtils.createLdapContext(ldapUrl.toString(), ar.dn,
          ar.password, getConnectTimeout(), null);
    }
    return ctx;
    return ConnectionUtils.createLdapContext(ldapUrl.toString(), ar.dn,
        ar.password, getConnectTimeout(), null);
  }
  /**
@@ -581,26 +483,16 @@
   * @return <CODE>true</CODE> if the LDAP URL is secure and <CODE>false</CODE>
   * otherwise.
   */
  public static boolean isSecureLDAPUrl(LDAPURL url) {
  private static boolean isSecureLDAPUrl(LDAPURL url) {
    return !LDAPURL.DEFAULT_SCHEME.equalsIgnoreCase(url.getScheme());
  }
  /**
   * Make an url from the specified arguments.
   * @param host the host.
   * @param port the port.
   * @param dn the dn.
   * @param secure whether it is a secure URL or not.
   * @return an LDAP URL from the specified arguments.
   */
  public static LDAPURL makeLDAPUrl(String host, int port, String dn,
      boolean secure) {
  private LDAPURL makeLDAPUrl(InitialLdapContext ctx) {
    return new LDAPURL(
        secure ? "ldaps" : LDAPURL.DEFAULT_SCHEME,
            host,
            port,
            dn,
        isSSL(ctx) ? "ldaps" : LDAPURL.DEFAULT_SCHEME,
            getHostName(ctx),
            getPort(ctx),
            "",
            null, // no attributes
            SearchScope.BASE_OBJECT,
            null, // No filter
@@ -645,20 +537,12 @@
        null); // No extensions
  }
  /**
   * Returns a collection of AuthRecord.
   * @return a collection of AuthRecord.
   */
  Collection<?> getRegisteredAuthentication() {
    return authTable.values();
  }
}
/**
 * A structure representing authentication data.
 */
class AuthRecord {
  LDAPURL ldapUrl;
  String dn;
  String password;
}
opendj3-server-dev/src/server/org/opends/server/replication/plugin/PersistentServerState.java
@@ -27,7 +27,7 @@
package org.opends.server.replication.plugin;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -57,7 +57,7 @@
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
   private final DN baseDn;
   private final DN baseDN;
   private final int serverId;
   private final ServerState state;
@@ -70,13 +70,13 @@
   * Create a new PersistentServerState based on an already existing
   * ServerState.
   *
   * @param baseDn    The baseDN for which the ServerState is created.
   * @param baseDN    The baseDN for which the ServerState is created.
   * @param serverId  The serverId.
   * @param state     The serverState.
   */
  PersistentServerState(DN baseDn, int serverId, ServerState state)
  PersistentServerState(DN baseDN, int serverId, ServerState state)
  {
    this.baseDn = baseDn;
    this.baseDN = baseDN;
    this.serverId = serverId;
    this.state = state;
    loadState();
@@ -114,7 +114,7 @@
  {
    if (!state.isSaved())
    {
      state.setSaved(updateStateEntry() == ResultCode.SUCCESS);
      state.setSaved(updateStateEntry());
    }
  }
@@ -164,16 +164,15 @@
       * Search the database entry that is used to periodically
       * save the ServerState
       */
      LinkedHashSet<String> attributes = new LinkedHashSet<String>(1);
      attributes.add(REPLICATION_STATE);
      final InternalSearchOperation search = getRootConnection().processSearch(
          baseDn, SearchScope.BASE_OBJECT, DereferenceAliasesPolicy.NEVER,
          0, 0, false, filter, attributes);
      if (((search.getResultCode() != ResultCode.SUCCESS)) &&
          ((search.getResultCode() != ResultCode.NO_SUCH_OBJECT)))
          baseDN, SearchScope.BASE_OBJECT, DereferenceAliasesPolicy.NEVER,
          0, 0, false, filter, Collections.singleton(REPLICATION_STATE));
      final ResultCode resultCode = search.getResultCode();
      if (resultCode != ResultCode.SUCCESS
          && resultCode != ResultCode.NO_SUCH_OBJECT)
      {
        logger.error(ERR_ERROR_SEARCHING_RUV, search.getResultCode().getName(), search,
                search.getErrorMessage(), baseDn);
                search.getErrorMessage(), baseDN);
        return null;
      }
      return getFirstResult(search);
@@ -197,15 +196,13 @@
    {
      SearchFilter filter = SearchFilter.createFilterFromString(
          "(&(objectclass=ds-cfg-replication-domain)"
          + "(ds-cfg-base-dn=" + baseDn + "))");
          + "(ds-cfg-base-dn=" + baseDN + "))");
      LinkedHashSet<String> attributes = new LinkedHashSet<String>(1);
      attributes.add(REPLICATION_STATE);
      final InternalSearchOperation op = getRootConnection().processSearch(
          DN.valueOf("cn=config"),
          SearchScope.SUBORDINATES,
          DereferenceAliasesPolicy.NEVER,
          1, 0, false, filter, attributes);
          1, 0, false, filter, Collections.singleton(REPLICATION_STATE));
      return getFirstResult(op);
    }
    catch (DirectoryException e)
@@ -238,12 +235,10 @@
  {
    AttributeType synchronizationStateType =
      DirectoryServer.getAttributeType(REPLICATION_STATE);
    List<Attribute> attrs =
      resultEntry.getAttribute(synchronizationStateType);
    List<Attribute> attrs = resultEntry.getAttribute(synchronizationStateType);
    if (attrs != null)
    {
      Attribute attr = attrs.get(0);
      for (ByteString value : attr)
      for (ByteString value : attrs.get(0))
      {
        update(new CSN(value.toString()));
      }
@@ -254,12 +249,12 @@
   * Save the current values of this PersistentState object
   * in the appropriate entry of the database.
   *
   * @return a ResultCode indicating if the method was successful.
   * @return a boolean indicating if the method was successful.
   */
  private ResultCode updateStateEntry()
  private boolean updateStateEntry()
  {
    // Generate a modify operation on the Server State baseDN Entry.
    ResultCode result = runUpdateStateEntry(baseDn);
    ResultCode result = runUpdateStateEntry(baseDN);
    if (result == ResultCode.NO_SUCH_OBJECT)
    {
      // The base entry does not exist yet in the database or has been deleted,
@@ -267,11 +262,10 @@
      SearchResultEntry configEntry = searchConfigEntry();
      if (configEntry != null)
      {
        DN configDN = configEntry.getName();
        result = runUpdateStateEntry(configDN);
        result = runUpdateStateEntry(configEntry.getName());
      }
    }
    return result;
    return result == ResultCode.SUCCESS;
  }
  /**
@@ -287,14 +281,12 @@
    ArrayList<ByteString> values = state.toASN1ArrayList();
    LDAPAttribute attr = new LDAPAttribute(REPLICATION_STATE, values);
    LDAPModification mod = new LDAPModification(ModificationType.REPLACE, attr);
    ArrayList<RawModification> mods = new ArrayList<RawModification>(1);
    mods.add(mod);
    RawModification mod = new LDAPModification(ModificationType.REPLACE, attr);
    ModifyOperationBasis op = new ModifyOperationBasis(getRootConnection(),
          nextOperationID(), nextMessageID(), null,
          ByteString.valueOf(serverStateEntryDN.toString()),
          mods);
          Collections.singletonList(mod));
    op.setInternalOperation(true);
    op.setSynchronizationOperation(true);
    op.setDontSynchronize(true);
@@ -302,7 +294,7 @@
    if (op.getResultCode() != ResultCode.SUCCESS)
    {
      logger.error(DEBUG_ERROR_UPDATING_RUV,
          op.getResultCode().getName(), op, op.getErrorMessage(), baseDn);
          op.getResultCode().getName(), op, op.getErrorMessage(), baseDN);
    }
    return op.getResultCode();
  }
@@ -357,7 +349,7 @@
      InternalSearchOperation op;
      try
      {
        op = LDAPReplicationDomain.searchForChangedEntries(baseDn,
        op = LDAPReplicationDomain.searchForChangedEntries(baseDN,
                serverStateMaxCSN, null);
      }
      catch (Exception  e)
@@ -369,7 +361,7 @@
      {
        // An error happened trying to search for the updates
        // Log an error
        logger.error(ERR_CANNOT_RECOVER_CHANGES, baseDn.toNormalizedString());
        logger.error(ERR_CANNOT_RECOVER_CHANGES, baseDN.toNormalizedString());
        return;
      }
@@ -394,7 +386,7 @@
      {
        // Update the serverState with the new maxCsn present in the database
        update(dbMaxCSN);
        logger.info(NOTE_SERVER_STATE_RECOVERY, baseDn.toNormalizedString(), dbMaxCSN);
        logger.info(NOTE_SERVER_STATE_RECOVERY, baseDN.toNormalizedString(), dbMaxCSN);
      }
    }
  }
@@ -410,4 +402,14 @@
  {
    return state.getCSN(serverId);
  }
  /** {@inheritDoc} */
  @Override
  public String toString()
  {
    return getClass().getSimpleName()
        + " baseDN=" + baseDN
        + " serverId=" + serverId
        + " " + REPLICATION_STATE + "=" + state;
  }
}
opendj3-server-dev/src/server/org/opends/server/replication/server/MessageHandler.java
@@ -292,31 +292,11 @@
           *           restart as usual
           *   load this change on the delayList
           */
          DBCursor<UpdateMsg> cursor = null;
          try
          {
            // fill the lateQueue
            cursor = replicationServerDomain.getCursorFrom(serverState);
            while (cursor.next() && isLateQueueBelowThreshold())
            {
              lateQueue.add(cursor.getRecord());
            }
          }
          catch (ChangelogException e)
          {
            logger.traceException(e);
          }
          finally
          {
            close(cursor);
          }
          /*
           * If the late queue is empty then we could not find any messages in
           * the replication log so the remote server is not late anymore.
           */
          fillLateQueue();
          if (lateQueue.isEmpty())
          {
            // we could not find any messages in the changelog
            // so the remote server is not late anymore.
            synchronized (msgQueue)
            {
              // Ensure we are below threshold so this server will follow the
@@ -330,8 +310,8 @@
          else
          {
            /*
             * if the first change in the lateQueue is also on the regular
             * queue, we can resume the processing from the regular queue
             * if the first change in the lateQueue is also on the regular queue,
             * we can resume the processing from the regular queue
             * -> set following to true and empty the lateQueue.
             */
            UpdateMsg msg = lateQueue.first();
@@ -353,7 +333,7 @@
        {
          // get the next change from the lateQueue
          UpdateMsg msg;
          synchronized (msgQueue)
          synchronized (msgQueue) // TODO JNR why synchronize(msgQueue) here?
          {
            msg = lateQueue.removeFirst();
          }
@@ -406,6 +386,27 @@
    return null;
  }
  private void fillLateQueue()
  {
    DBCursor<UpdateMsg> cursor = null;
    try
    {
      cursor = replicationServerDomain.getCursorFrom(serverState);
      while (cursor.next() && isLateQueueBelowThreshold())
      {
        lateQueue.add(cursor.getRecord());
      }
    }
    catch (ChangelogException e)
    {
      logger.traceException(e);
    }
    finally
    {
      close(cursor);
    }
  }
  private boolean isLateQueueBelowThreshold()
  {
    return lateQueue.count() < 100 && lateQueue.bytesCount() < 50000;
@@ -425,16 +426,14 @@
      {
        if (!msgQueue.isEmpty())
        {
          UpdateMsg msg = msgQueue.first();
          result = msg.getCSN();
          result = msgQueue.first().getCSN();
        }
      }
      else
      {
        if (!lateQueue.isEmpty())
        {
          UpdateMsg msg = lateQueue.first();
          result = msg.getCSN();
          result = lateQueue.first().getCSN();
        }
        else
        {
opendj3-server-dev/src/server/org/opends/server/replication/server/MsgQueue.java
@@ -26,7 +26,6 @@
 */
package org.opends.server.replication.server;
import java.util.NavigableMap;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import java.util.TreeMap;
@@ -38,13 +37,21 @@
/**
 * This class is used to build ordered lists of UpdateMsg.
 * The order is defined by the order of the CSN of the UpdateMsg.
 * @ThreadSafe
 */
public class MsgQueue
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  private NavigableMap<CSN, UpdateMsg> map = new TreeMap<CSN, UpdateMsg>();
  private TreeMap<CSN, UpdateMsg> map = new TreeMap<CSN, UpdateMsg>();
  /**
   * FIXME JNR to be investigated:
   * I strongly suspect that we could replace this field
   * by using the synchronized keyword on each method.
   * However, MessageHandler is weirdly synchronizing on msgQueue field
   * even though it is touching the lateQueue field (?!?).
   */
  private final Object lock = new Object();
  /** The total number of bytes for all the message in the queue. */
@@ -112,7 +119,7 @@
  {
    synchronized (lock)
    {
      UpdateMsg msgSameCSN = map.put(update.getCSN(), update);
      final UpdateMsg msgSameCSN = map.put(update.getCSN(), update);
      if (msgSameCSN != null)
      {
        try
@@ -123,11 +130,11 @@
          {
            // Adding 2 msgs with the same CSN is ok only when
            // the 2 msgs are the same
            bytesCount += (update.size() - msgSameCSN.size());
            bytesCount += update.size() - msgSameCSN.size();
            logger.error(ERR_RSQUEUE_DIFFERENT_MSGS_WITH_SAME_CN, msgSameCSN.getCSN(), msgSameCSN, update);
          }
        }
        catch(Exception e)
        catch (Exception e)
        {
          logger.traceException(e);
        }
@@ -149,10 +156,12 @@
  {
    synchronized (lock)
    {
      UpdateMsg update = map.get(map.firstKey());
      // FIXME JNR replace next 2 lines with just that one:
      // final UpdateMsg update = map.pollFirstEntry().getValue();
      final UpdateMsg update = map.get(map.firstKey());
      map.remove(update.getCSN());
      bytesCount -= update.size();
      if ((map.size() == 0) && (bytesCount != 0))
      if (map.isEmpty() && bytesCount != 0)
      {
        // should never happen
        logger.error(ERR_BYTE_COUNT, bytesCount);
@@ -197,18 +206,33 @@
   * message. If the passed in message is not contained in the current queue,
   * then all messages will be removed from it.
   *
   * @param msg
   * @param finalMsg
   *          the final message to reach when consuming messages from this queue
   */
  public void consumeUpTo(UpdateMsg msg)
  public void consumeUpTo(UpdateMsg finalMsg)
  {
    UpdateMsg msg1;
    // FIXME this code could be more efficient if the msgQueue could call the
    // following code (to be tested):
    // if (!map.containsKey(finalMsg.getCSN())) {
    // map.clear();
    // } else {
    // map.headMap(finalMsg.getCSN(), true).clear();
    // }
    final CSN finalCSN = finalMsg.getCSN();
    UpdateMsg msg;
    do
    {
      // FIXME this code could be more efficient if the msgQueue could call the
      // following code (to be tested):
      // map.headMap(msg.getCSN(), true).clear()
      msg1 = removeFirst();
    } while (!msg.getCSN().equals(msg1.getCSN()));
      msg = removeFirst();
    }
    while (!finalCSN.equals(msg.getCSN()));
  }
  /** {@inheritDoc} */
  @Override
  public String toString()
  {
    return getClass().getSimpleName() + " bytesCount=" + bytesCount + " queue="
        + map.values();
  }
}
opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/je/JEReplicaDBCursor.java
@@ -30,7 +30,7 @@
import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.replication.server.changelog.api.ChangelogException;
import org.opends.server.replication.server.changelog.api.DBCursor;
import org.opends.server.replication.server.changelog.je.ReplicationDB.*;
import org.opends.server.replication.server.changelog.je.ReplicationDB.ReplServerDBCursor;
/**
 * Berkeley DB JE implementation of {@link DBCursor}.
@@ -89,11 +89,7 @@
    final ReplServerDBCursor localCursor = cursor;
    currentChange = localCursor != null ? localCursor.next() : null;
    if (currentChange != null)
    {
      lastNonNullCurrentCSN = currentChange.getCSN();
    }
    else
    if (currentChange == null)
    {
      synchronized (this)
      {
@@ -105,13 +101,14 @@
        // and fixing such issue with unit tests.
        cursor = db.openReadCursor(lastNonNullCurrentCSN);
        currentChange = cursor.next();
        if (currentChange != null)
        {
          lastNonNullCurrentCSN = currentChange.getCSN();
        }
      }
    }
    return currentChange != null;
    if (currentChange != null)
    {
      lastNonNullCurrentCSN = currentChange.getCSN();
      return true;
    }
    return false;
  }
  /** {@inheritDoc} */
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java
@@ -26,28 +26,30 @@
 */
package org.opends.server.monitors;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import org.forgerock.opendj.ldap.SearchScope;
import org.opends.server.types.DN;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.SearchResultEntry;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import static org.forgerock.opendj.ldap.SearchScope.*;
import static org.opends.server.protocols.internal.InternalClientConnection.*;
import static org.opends.server.types.SearchFilter.*;
import static org.testng.Assert.*;
/**
 * Interacts with the Directory Server monitor providers by retrieving the
 * monitor entries with internal searches.
 */
@SuppressWarnings("javadoc")
public class InternalSearchMonitorTestCase
       extends MonitorTestCase
{
@@ -55,20 +57,15 @@
  /**
   * Ensures that the Directory Server is started.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  @BeforeClass
  public void startServer() throws Exception
  {
    TestCaseUtils.startServer();
    DirectoryServer.registerMonitorProvider(testMonitorProvider);
  }
  @AfterClass()
  @AfterClass
  public void deregisterTestMonitor()
  {
    DirectoryServer.deregisterMonitorProvider(testMonitorProvider);
@@ -76,20 +73,14 @@
  /**
   * Uses an internal subtree search to retrieve the monitor entries.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test
  public void testWithSubtreeMonitorSearch()
         throws Exception
  public void testWithSubtreeMonitorSearch() throws Exception
  {
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(DN.valueOf("cn=monitor"), SearchScope.WHOLE_SUBTREE,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS,
        "Failed to search cn=monitor subtree. Got error message: " + searchOperation.getErrorMessage());
    InternalSearchOperation op = getRootConnection().processSearch(
        "cn=monitor", WHOLE_SUBTREE, "(objectClass=*)");
    assertEquals(op.getResultCode(), ResultCode.SUCCESS,
        "Failed to search cn=monitor subtree. Got error message: " + op.getErrorMessage());
  }
@@ -102,18 +93,14 @@
  @DataProvider(name = "monitorNames")
  public Object[][] getMonitorNames()
  {
    ArrayList<String> monitorNames = new ArrayList<String>();
    for (String name : DirectoryServer.getMonitorProviders().keySet())
    {
      monitorNames.add(name);
    }
    Set<String> monitorNames = DirectoryServer.getMonitorProviders().keySet();
    Iterator<String> it = monitorNames.iterator();
    Object[][] nameArray = new Object[monitorNames.size()][1];
    for (int i=0; i < nameArray.length; i++)
    {
      nameArray[i] = new Object[] { monitorNames.get(i) };
      nameArray[i] = new Object[] { it.next() };
    }
    return nameArray;
  }
@@ -123,55 +110,39 @@
   * Uses a set of internal base-level searches to retrieve the monitor entries.
   *
   * @param  monitorName  The name of the monitor entry to retrieve.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "monitorNames")
  public void testWithBaseObjectMonitorSearch(String monitorName)
         throws Exception
  public void testWithBaseObjectMonitorSearch(String monitorName) throws Exception
  {
    // could be more than one level
    DN monitorDN = DN.valueOf("cn="+monitorName+",cn=monitor");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(monitorDN,
              SearchScope.BASE_OBJECT,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS,
        "Failed to read " + monitorDN + " entry. Got error message: " + searchOperation.getErrorMessage());
    final String monitorDN = "cn="+monitorName+",cn=monitor";
    InternalSearchOperation op = getRootConnection().processSearch(
        monitorDN, BASE_OBJECT, "(objectClass=*)");
    assertEquals(op.getResultCode(), ResultCode.SUCCESS,
        "Failed to read " + monitorDN + " entry. Got error message: " + op.getErrorMessage());
  }
  /**
   * Uses an internal subtree search to retrieve the monitor entries, then
   * verifies that the resulting entry DNs can be used to get the same
   * entries with a base object search.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test
  public void testWithSubtreeAndBaseMonitorSearch()
         throws Exception
  public void testWithSubtreeAndBaseMonitorSearch() throws Exception
  {
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(DN.valueOf("cn=monitor"), SearchScope.WHOLE_SUBTREE,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS,
        "Failed to search cn=monitor subtree. Got error message: " + searchOperation.getErrorMessage());
    final InternalClientConnection conn = getRootConnection();
    InternalSearchOperation op = conn.processSearch(
        "cn=monitor", WHOLE_SUBTREE, "(objectClass=*)");
    assertEquals(op.getResultCode(), ResultCode.SUCCESS,
        "Failed to search cn=monitor subtree. Got error message: " + op.getErrorMessage());
    for (SearchResultEntry sre : searchOperation.getSearchEntries())
    for (SearchResultEntry sre : op.getSearchEntries())
    {
      SearchFilter filter =
           SearchFilter.createFilterFromString("(objectClass=*)");
      InternalSearchOperation readOperation =
           conn.processSearch(sre.getName(), SearchScope.BASE_OBJECT, filter);
      assertEquals(readOperation.getResultCode(), ResultCode.SUCCESS,
          "Failed to read " + sre.getName() + " entry. Got error message: " + readOperation.getErrorMessage());
      final InternalSearchOperation readOp = conn.processSearch(
          sre.getName(), BASE_OBJECT, createFilterFromString("(objectClass=*)"));
      assertEquals(readOp.getResultCode(), ResultCode.SUCCESS,
          "Failed to read " + sre.getName() + " entry. Got error message: " + readOp.getErrorMessage());
    }
  }
}
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java
@@ -28,17 +28,13 @@
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.Socket;
import java.util.*;
import org.assertj.core.api.Assertions;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DereferenceAliasesPolicy;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.*;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.server.ExternalChangelogDomainCfg;
import org.opends.server.api.Backend;
@@ -67,7 +63,12 @@
import org.opends.server.tools.LDAPSearch;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.*;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.types.Attribute;
import org.opends.server.types.Attributes;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.RDN;
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.TimeThread;
import org.opends.server.workflowelement.externalchangelog.ECLSearchOperation;
@@ -79,11 +80,11 @@
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.*;
import static org.forgerock.opendj.ldap.ResultCode.*;
import static org.opends.messages.ReplicationMessages.*;
import static org.opends.server.TestCaseUtils.*;
import static org.opends.server.controls.PersistentSearchChangeType.*;
import static org.opends.server.replication.protocol.OperationContext.*;
import static org.forgerock.opendj.ldap.ResultCode.*;
import static org.opends.server.util.StaticUtils.*;
import static org.testng.Assert.*;
@@ -163,17 +164,7 @@
    debugInfo("configure", "ReplicationServer created"+replicationServer);
  }
  /**
   * Launcher.
   */
  @Test(enabled=true)
  public void PreTest() throws Exception
  {
    // No RSDomain created yet => RS only case => ECL is not a supported
    ECLIsNotASupportedSuffix();
  }
  @Test(enabled=true, dependsOnMethods = { "PreTest"})
  @Test(enabled = true, dependsOnMethods = { "TestECLIsNotASupportedSuffix" })
  public void PrimaryTest() throws Exception
  {
    replicationServer.getChangelogDB().setPurgeDelay(0);
@@ -194,27 +185,6 @@
    ECLCompatTestLimits(1,4,true);
  }
  @Test(enabled=false, dependsOnMethods = { "PrimaryTest"})
  public void TestWithTwoDomains() throws Exception
  {
    replicationServer.getChangelogDB().setPurgeDelay(0);
    // Test with a mix of domains, a mix of DSes
    ECLTwoDomains();
  }
  @Test(enabled=false, dependsOnMethods = { "PrimaryTest"})
  public void TestAfterChangelogTrim() throws Exception
  {
    // Test ECL after changelog trimming
    ECLAfterChangelogTrim();
  }
  @Test(enabled=true, dependsOnMethods = { "PrimaryTest"})
  public void TestAfterDomainIsRemoved() throws Exception
  {
    ECLAfterDomainIsRemoved();
  }
  @Test(enabled=true, dependsOnMethods = { "PrimaryTest"})
  public void TestWithAndWithoutControl() throws Exception
  {
@@ -228,27 +198,6 @@
    ECLCompatWriteReadAllOps(5);
  }
  @Test(enabled = true, dependsOnMethods = { "TestWithAndWithoutControl" })
  public void TestWithIncludeAttributes() throws Exception
  {
    ECLIncludeAttributes();
  }
  @Test(enabled=true, dependsOnMethods = { "PrimaryTest"})
  public void TestChangeTimeHeartBeat() throws Exception
  {
    ChangeTimeHeartbeatTest();
  }
  @Test(enabled=true, dependsOnMethods = { "PrimaryTest"})
  public void TestOperationalAttributesNotVisibleOutsideRootDSE() throws Exception
  {
    // Test that ECL Operational, virtual attributes are not visible
    // outside rootDSE. Next test will test access in RootDSE.
    // This one checks in data.
    ECLOperationalAttributesFailTest();
  }
  @Test(enabled=false, dependsOnMethods = { "PrimaryTest"})
  public void PrimaryFullTest() throws Exception
  {
@@ -281,10 +230,8 @@
    // Test all types of ops.
    ECLAllOps(); // Do not clean the db for the next test
    // Test that ECL Operational, virtual attributes are not visible
    // outside rootDSE. Next test will test access in RootDSE.
    // This one checks in data.
    ECLOperationalAttributesFailTest();
    // Test after this one will test access in RootDSE. This one checks in data.
    TestECLOperationalAttributesNotVisibleOutsideRootDSE();
    // First and last should be ok whenever a request has been done or not
    // in compat mode.
@@ -298,27 +245,20 @@
    ECLRemoteNonEmpty();
  }
  /** Persistent search with changesOnly request */
  @Test(enabled=false, groups="slow", dependsOnMethods = { "PrimaryTest"})
  public void FullTestPersistentSearchWithChangesOnlyRequest() throws Exception
  {
    // Persistent search with changesOnly request
    ECLPsearch(true, false);
  }
  /** Persistent search with init values request */
  @Test(enabled=false, groups="slow", dependsOnMethods = { "PrimaryTest"})
  public void FullTestPersistentSearchWithInitValuesRequest() throws Exception
  {
    // Persistent search with init values request
    ECLPsearch(false, false);
  }
  @Test(enabled=false, groups="slow", dependsOnMethods = { "PrimaryTest"})
  public void FullTestSimultaneousPersistentSearches() throws Exception
  {
    // Simultaneous psearches
    ECLSimultaneousPsearches();
  }
  // TODO:ECL Test SEARCH abandon and check everything shutdown and cleaned
  // TODO:ECL Test PSEARCH abandon and check everything shutdown and cleaned
  // TODO:ECL Test invalid DN in cookie returns UNWILLING + message
@@ -412,7 +352,9 @@
    assertEquals(ico.getErrorMessage().toMessage(), NOTE_SEARCH_CHANGELOG_INSUFFICIENT_PRIVILEGES.get());
  }
  private void ECLIsNotASupportedSuffix() throws Exception
  /** No RSDomain created yet => RS only case => ECL is not a supported. */
  @Test(enabled = true)
  public void TestECLIsNotASupportedSuffix() throws Exception
  {
    ECLCompatTestLimits(0,0, false);
  }
@@ -472,11 +414,15 @@
  }
  /**
   * Objectives
   *   - Test that everything is ok with changes on 2 suffixes
   * Procedure
   *   - From 1 remote ECL session,
   *   - Test simple update to be received from 2 suffixes
   * Objectives:
   * <ul>
   * <li>Test that everything is ok with changes on 2 suffixes</li>
   * </ul>
   * Procedure:
   * <ul>
   * <li>From 1 remote ECL session,</li>
   * <li>Test simple update to be received from 2 suffixes</li>
   * </ul>
   */
  private void ECLRemoteNonEmpty() throws Exception
  {
@@ -560,7 +506,7 @@
    debugInfo(tn, "Starting test\n\n");
    // root entry returned
    searchOnChangelog("(objectclass=*)", Collections.<String> emptySet(), createControls(""),
    searchOnChangelog("(objectclass=*)", Collections.<String> emptySet(), createCookieControl(""),
        1, ResultCode.SUCCESS, tn);
    debugInfo(tn, "Ending test successfully");
@@ -571,12 +517,11 @@
   * @param cookie The provided cookie.
   * @return The built list of controls.
   */
  private List<Control> createControls(String cookie) throws DirectoryException
  private List<Control> createCookieControl(String cookie) throws DirectoryException
  {
    final MultiDomainServerState state = new MultiDomainServerState(cookie);
    final List<Control> controls = new ArrayList<Control>(1);
    controls.add(new ExternalChangelogRequestControl(true, state));
    return controls;
    final Control cookieControl = new ExternalChangelogRequestControl(true, state);
    return newList(cookieControl);
  }
  /**
@@ -668,11 +613,16 @@
  }
  /**
   * From embedded ECL Search ECL with 4 messages on 2 suffixes from 2 brokers
   * From embedded ECL Search ECL with 4 messages on 2 suffixes from 2 brokers.
   * Test with a mix of domains, a mix of DSes.
   */
  private void ECLTwoDomains() throws Exception
  @Test(enabled=false, dependsOnMethods = { "PrimaryTest"})
  public void TestECLWithTwoDomains() throws Exception
  {
    String tn = "ECLTwoDomains";
    replicationServer.getChangelogDB().setPurgeDelay(0);
    String tn = "TestECLWithTwoDomains";
    debugInfo(tn, "Starting test");
    ReplicationBroker s1test = null;
@@ -882,7 +832,7 @@
      throws Exception
  {
    debugInfo(testName, "Search with cookie=[" + cookie + "] filter=[" + filterString + "]");
    return searchOnChangelog(filterString, ALL_ATTRIBUTES, createControls(cookie),
    return searchOnChangelog(filterString, ALL_ATTRIBUTES, createCookieControl(cookie),
        expectedNbEntries, expectedResultCode, testName);
  }
@@ -927,9 +877,10 @@
  }
  /** Test ECL content after replication changelogDB trimming */
  private void ECLAfterChangelogTrim() throws Exception
  @Test(enabled=false, dependsOnMethods = { "PrimaryTest"})
  public void testECLAfterChangelogTrim() throws Exception
  {
    String testName = "ECLAfterChangelogTrim";
    String testName = "testECLAfterChangelogTrim";
    debugInfo(testName, "Starting test");
    ReplicationBroker server01 = null;
@@ -994,9 +945,10 @@
  }
  /** Test ECL content after a domain has been removed. */
  private void ECLAfterDomainIsRemoved() throws Exception
  @Test(enabled=true, dependsOnMethods = { "PrimaryTest"})
  public void testECLAfterDomainIsRemoved() throws Exception
  {
    String testName = "ECLAfterDomainIsRemoved";
    String testName = "testECLAfterDomainIsRemoved";
    debugInfo(testName, "Starting test");
    ReplicationBroker server01 = null;
@@ -1133,7 +1085,7 @@
          baseUUID,
          entry.getObjectClassAttribute(),
          entry.getAttributes(),
          new ArrayList<Attribute>());
          Collections.<Attribute> emptyList());
      server01.publish(addMsg);
      debugInfo(tn, " publishes " + addMsg.getCSN());
@@ -1250,31 +1202,27 @@
    assertThat(actualDN).isEqualToIgnoringCase(expectedDN);
  }
  private List<String> getControls(String resultString)
  private List<String> getControls(String resultString) throws Exception
  {
    StringReader r=new StringReader(resultString);
    BufferedReader br=new BufferedReader(r);
    List<String> ctrlList = new ArrayList<String>();
    try {
      while(true) {
        String s = br.readLine();
        if(s == null)
        {
          break;
        }
        if(!s.startsWith("#"))
        {
          continue;
        }
        String[] a=s.split(": ");
        if(a.length != 2)
        {
          break;
        }
        ctrlList.add(a[1]);
    final BufferedReader br = new BufferedReader(new StringReader(resultString));
    final List<String> ctrlList = new ArrayList<String>();
    while (true)
    {
      final String s = br.readLine();
      if (s == null)
      {
        break;
      }
    } catch (IOException e) {
      assertEquals(0, 1, e.getMessage());
      if (!s.startsWith("#"))
      {
        continue;
      }
      final String[] a = s.split(": ");
      if (a.length != 2)
      {
        break;
      }
      ctrlList.add(a[1]);
    }
    return ctrlList;
  }
@@ -1433,7 +1381,7 @@
      // Creates cookie control
      String cookie = "";
      List<Control> controls = createControls(cookie);
      List<Control> controls = createCookieControl(cookie);
      if (compatMode)
      {
        cookie = null;
@@ -1575,7 +1523,7 @@
            createSearchRequest("(targetDN=*directpsearch*,o=test)", null);
        debugInfo(tn, "ACI test : sending search");
        message = new LDAPMessage(2, searchRequest, createControls(""));
        message = new LDAPMessage(2, searchRequest, createCookieControl(""));
        w.writeMessage(message);
        searchesDone=0;
@@ -1654,11 +1602,12 @@
  }
  /**
   * Test parallel simultaneous psearch with different filters.
   * Test parallel simultaneous persistent search with different filters.
   */
  private void ECLSimultaneousPsearches() throws Exception
  @Test(enabled = false, groups = "slow", dependsOnMethods = { "PrimaryTest" })
  public void FullTestSimultaneousPersistentSearches() throws Exception
  {
    String tn = "ECLSimultaneousPsearches";
    String tn = "FullTestSimultaneousPersistentSearches";
    debugInfo(tn, "Starting test \n\n");
    Socket s1 = null, s2 = null, s3 = null;
    ReplicationBroker server01 = null;
@@ -1724,7 +1673,7 @@
      // Creates cookie control
      String cookie = "";
      List<Control> controls = createControls(cookie);
      List<Control> controls = createCookieControl(cookie);
      if (compatMode)
      {
        cookie = null;
@@ -2163,9 +2112,10 @@
  /**
   * FIXME this test actually tests nothing: there are no asserts.
   */
  private void ChangeTimeHeartbeatTest() throws Exception
  @Test(enabled = true, dependsOnMethods = { "PrimaryTest" })
  public void testChangeTimeHeartbeat() throws Exception
  {
    String tn = "ChangeTimeHeartbeatTest";
    String tn = "testChangeTimeHeartbeat";
    debugInfo(tn, "Starting test");
    ReplicationBroker s1test = null;
    ReplicationBroker s2test = null;
@@ -2292,7 +2242,7 @@
          baseUUID,
          entry.getObjectClassAttribute(),
          entry.getAttributes(),
          new ArrayList<Attribute>());
          Collections.<Attribute> emptyList());
      server01.publish(addMsg);
      debugInfo(tn, " publishes " + addMsg.getCSN());
@@ -2604,9 +2554,13 @@
    debugInfo(tn, "Ending test with success");
  }
  private void ECLOperationalAttributesFailTest() throws Exception
  /**
   * Test that ECL Operational, virtual attributes are not visible outside rootDSE.
   */
  @Test(enabled = true, dependsOnMethods = { "PrimaryTest" })
  public void TestECLOperationalAttributesNotVisibleOutsideRootDSE() throws Exception
  {
    String tn = "ECLOperationalAttributesFailTest";
    String tn = "TestECLOperationalAttributesNotVisibleOutsideRootDSE";
    // The goal is to verify that the Changelog attributes are not
    // available in other entries. We u
    debugInfo(tn, "Starting test \n\n");
@@ -2623,9 +2577,7 @@
            0, // Time limit
            false, // Types only
            "(objectclass=*)",
            attributes,
            NO_CONTROL,
            null);
            attributes);
    waitOpResult(searchOp, ResultCode.SUCCESS);
    final List<SearchResultEntry> entries = searchOp.getSearchEntries();
@@ -2721,9 +2673,7 @@
        0, // Time limit
        false, // Types only
        "(objectclass=*)",
        attributes,
        NO_CONTROL,
        null);
        attributes);
    waitOpResult(searchOp, ResultCode.SUCCESS);
    return searchOp;
  }
@@ -2774,11 +2724,12 @@
  }
  /**
   * Test ECl entry attributes, and there configuration.
   * Test ECl entry attributes, and their configuration.
   */
  private void ECLIncludeAttributes() throws Exception
  @Test(enabled = true, dependsOnMethods = { "TestWithAndWithoutControl" })
  public void TestECLWithIncludeAttributes() throws Exception
  {
    String tn = "ECLIncludeAttributes";
    String tn = "TestECLWithIncludeAttributes";
    debugInfo(tn, "Starting test\n\n");
    final String backendId3 = "test3";
@@ -2953,9 +2904,7 @@
  private List<Modification> createMods(String attributeName, String valueString)
  {
    Attribute attr = Attributes.create(attributeName, valueString);
    List<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE, attr));
    return mods;
    return newList(new Modification(ModificationType.REPLACE, attr));
  }
  private Entry parseIncludedAttributes(SearchResultEntry resultEntry,
@@ -2970,8 +2919,7 @@
    return TestCaseUtils.makeEntry(ldif);
  }
  private void waitOpResult(Operation operation, ResultCode expectedResult)
      throws Exception
  private void waitOpResult(Operation operation, ResultCode expectedResult) throws Exception
  {
    int i = 0;
    while (operation.getResultCode() == ResultCode.UNDEFINED