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

Jean-Noel Rouvignac
22.23.2014 25862fdf888ed23207ab51937a43e6f9ad41d805
opendj3-server-dev/src/guitools/org/opends/guitools/controlpanel/util/ReadOnlyConfigFileHandler.java
@@ -90,6 +90,7 @@
  @Override
  public void finalizeConfigHandler()
  {
    finalizeBackend();
  }
  /** {@inheritDoc} */
@@ -291,12 +292,6 @@
  /** {@inheritDoc} */
  @Override
  public void finalizeBackend()
  {
  }
  /** {@inheritDoc} */
  @Override
  public DN[] getBaseDNs()
  {
    return baseDNs;
opendj3-server-dev/src/server/org/opends/server/api/Backend.java
@@ -29,7 +29,9 @@
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.config.server.ConfigException;
@@ -40,6 +42,8 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PersistentSearch.CancellationCallback;
import org.opends.server.core.SearchOperation;
import org.opends.server.monitors.BackendMonitor;
import org.opends.server.types.AttributeType;
@@ -99,6 +103,10 @@
  /** The writability mode for this backend. */
  private WritabilityMode writabilityMode = WritabilityMode.ENABLED;
  /** The set of persistent searches registered with this backend. */
  private final ConcurrentLinkedQueue<PersistentSearch> persistentSearches =
      new ConcurrentLinkedQueue<PersistentSearch>();
  /**
   * Configure this backend based on the information in the provided
   * configuration.
@@ -166,16 +174,26 @@
  /**
   * Performs any necessary work to finalize this backend, including
   * closing any underlying databases or connections and deregistering
   * any suffixes that it manages with the Directory Server.  This may
   * any suffixes that it manages with the Directory Server. This may
   * be called during the Directory Server shutdown process or if a
   * backend is disabled with the server online.  It must not return
   * until the backend is closed.
   * <BR><BR>
   * This method may not throw any exceptions.  If any problems are
   * encountered, then they may be logged but the closure should
   * progress as completely as possible.
   * backend is disabled with the server online.
   * It must not return until the backend is closed.
   * <p>
   * This method may not throw any exceptions. If any problems are encountered,
   * then they may be logged but the closure should progress as completely as
   * possible.
   * <p>
   * This method must be called by all overriding methods with
   * <code>super.finalizeBackend()</code>.
   */
  public abstract void finalizeBackend();
  public void finalizeBackend()
  {
    for (PersistentSearch psearch : persistentSearches)
    {
      psearch.cancel();
    }
    persistentSearches.clear();
  }
@@ -887,7 +905,39 @@
    return backendMonitor;
  }
  /**
   * Registers the provided persistent search operation with this backend so
   * that it will be notified of any add, delete, modify, or modify DN
   * operations that are performed.
   *
   * @param persistentSearch
   *          The persistent search operation to register with this backend
   */
  public void registerPersistentSearch(PersistentSearch persistentSearch)
  {
    persistentSearches.add(persistentSearch);
    persistentSearch.registerCancellationCallback(new CancellationCallback()
    {
      @Override
      public void persistentSearchCancelled(PersistentSearch psearch)
      {
        persistentSearches.remove(psearch);
      }
    });
  }
  /**
   * Returns the persistent searches currently active against this local
   * backend.
   *
   * @return the list of persistent searches currently active against this local
   *         backend
   */
  public Queue<PersistentSearch> getPersistentSearches()
  {
    return persistentSearches;
  }
  /**
   * Sets the backend monitor for this backend.
opendj3-server-dev/src/server/org/opends/server/backends/BackupBackend.java
@@ -201,6 +201,7 @@
  @Override
  public void finalizeBackend()
  {
    super.finalizeBackend();
    currentConfig.removeBackupChangeListener(this);
    try
opendj3-server-dev/src/server/org/opends/server/backends/MonitorBackend.java
@@ -64,7 +64,8 @@
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /** The set of user-defined attributes that will be included in the base
  /**
   * The set of user-defined attributes that will be included in the base
   * monitor entry.
   */
  private ArrayList<Attribute> userDefinedAttributes;
@@ -331,6 +332,7 @@
  @Override
  public void finalizeBackend()
  {
    super.finalizeBackend();
    currentConfig.removeMonitorChangeListener(this);
    try
    {
opendj3-server-dev/src/server/org/opends/server/backends/NullBackend.java
@@ -219,6 +219,7 @@
  @Override
  public synchronized void finalizeBackend()
  {
    super.finalizeBackend();
    for (DN dn : baseDNs)
    {
      try
opendj3-server-dev/src/server/org/opends/server/backends/RootDSEBackend.java
@@ -286,6 +286,7 @@
  @Override
  public void finalizeBackend()
  {
    super.finalizeBackend();
    currentConfig.removeChangeListener(this);
  }
opendj3-server-dev/src/server/org/opends/server/backends/SchemaBackend.java
@@ -495,6 +495,7 @@
  @Override
  public void finalizeBackend()
  {
    super.finalizeBackend();
    currentConfig.removeSchemaChangeListener(this);
    for (DN baseDN : baseDNs)
opendj3-server-dev/src/server/org/opends/server/backends/TrustStoreBackend.java
@@ -346,6 +346,7 @@
  @Override
  public void finalizeBackend()
  {
    super.finalizeBackend();
    configuration.addTrustStoreChangeListener(this);
    try
opendj3-server-dev/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -312,7 +312,7 @@
  @Override
  public void finalizeBackend()
  {
    // Deregister as a change listener.
    super.finalizeBackend();
    cfg.removeLocalDBChangeListener(this);
    // Deregister our base DNs.
@@ -349,10 +349,7 @@
    }
    // Checksum this db environment and register its offline state id/checksum.
    DirectoryServer.registerOfflineBackendStateID(this.getBackendID(),
      checksumDbEnv());
    //Deregister the alert generator.
    DirectoryServer.registerOfflineBackendStateID(getBackendID(), checksumDbEnv());
    DirectoryServer.deregisterAlertGenerator(this);
    // Make sure the thread counts are zero for next initialization.
opendj3-server-dev/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -263,9 +263,9 @@
  @Override
  public void finalizeBackend()
  {
    super.finalizeBackend();
    currentConfig.removeTaskChangeListener(this);
    try
    {
      taskScheduler.stopScheduler();
opendj3-server-dev/src/server/org/opends/server/core/PersistentSearch.java
@@ -107,10 +107,8 @@
      psearch.isCancelled = true;
      // The persistent search can no longer be cancelled.
      psearch.searchOperation.getClientConnection().deregisterPersistentSearch(
          psearch);
      psearch.searchOperation.getClientConnection().deregisterPersistentSearch(psearch);
      //Decrement of psearch count maintained by the server.
      DirectoryServer.deregisterPersistentSearch();
      // Notify any cancellation callbacks.
@@ -152,25 +150,33 @@
  /** The reference to the associated search operation. */
  private final SearchOperation searchOperation;
  /**
   * Indicates whether to only return entries that have been updated since the
   * beginning of the search.
   */
  private final boolean changesOnly;
  /**
   * Creates a new persistent search object with the provided
   * information.
   * Creates a new persistent search object with the provided information.
   *
   * @param searchOperation
   *          The search operation for this persistent search.
   * @param changeTypes
   *          The change types for which changes should be examined.
   * @param changesOnly
   *          whether to only return entries that have been updated since the
   *          beginning of the search
   * @param returnECs
   *          Indicates whether to include entry change notification
   *          controls in search result entries sent to the client.
   *          Indicates whether to include entry change notification controls in
   *          search result entries sent to the client.
   */
  public PersistentSearch(SearchOperation searchOperation,
      Set<PersistentSearchChangeType> changeTypes, boolean returnECs)
      Set<PersistentSearchChangeType> changeTypes, boolean changesOnly,
      boolean returnECs)
  {
    this.searchOperation = searchOperation;
    this.changeTypes = changeTypes;
    this.changesOnly = changesOnly;
    this.returnECs = returnECs;
  }
@@ -232,6 +238,18 @@
  }
  /**
   * Returns whether only entries updated after the beginning of this persistent
   * search should be returned.
   *
   * @return true if only entries updated after the beginning of this search
   *         should be returned, false otherwise
   */
  public boolean isChangesOnly()
  {
    return changesOnly;
  }
  /**
   * Notifies the persistent searches that an entry has been added.
   *
   * @param entry
opendj3-server-dev/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -752,6 +752,7 @@
  @Override
  public void finalizeConfigHandler()
  {
    finalizeBackend();
    try
    {
      DirectoryServer.deregisterBaseDN(configRootEntry.getDN());
@@ -764,13 +765,6 @@
  /** {@inheritDoc} */
  @Override
  public void finalizeBackend()
  {
    // No implementation is required.
  }
  /** {@inheritDoc} */
  @Override
  public ConfigEntry getConfigRootEntry()
         throws ConfigException
  {
opendj3-server-dev/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -85,7 +85,6 @@
import org.opends.server.types.operation.*;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.TimeThread;
import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation;
import static org.forgerock.opendj.ldap.ResultCode.*;
@@ -478,7 +477,7 @@
    storeECLConfiguration(configuration);
    solveConflictFlag = isSolveConflict(configuration);
    Backend backend = getBackend();
    Backend<?> backend = getBackend();
    if (backend == null)
    {
      throw new ConfigException(ERR_SEARCHING_DOMAIN_BACKEND.get(
@@ -3468,7 +3467,7 @@
  private long exportBackend(OutputStream output, boolean checksumOutput)
      throws DirectoryException
  {
    Backend backend = getBackend();
    Backend<?> backend = getBackend();
    //  Acquire a shared lock for the backend.
    try
@@ -3601,7 +3600,7 @@
   * @throws DirectoryException
   *           If the backend could not be disabled or locked exclusively.
   */
  private void preBackendImport(Backend backend) throws DirectoryException
  private void preBackendImport(Backend<?> backend) throws DirectoryException
  {
    // Stop saving state
    stateSavingDisabled = true;
@@ -3629,10 +3628,9 @@
  @Override
  protected void importBackend(InputStream input) throws DirectoryException
  {
    Backend<?> backend = getBackend();
    LDIFImportConfig importConfig = null;
    Backend backend = getBackend();
    ImportExportContext ieCtx = getImportExportContext();
    try
    {
@@ -3718,7 +3716,7 @@
   * @param backend The backend implied in the import.
   * @exception DirectoryException Thrown when an error occurs.
   */
  private void closeBackendImport(Backend backend) throws DirectoryException
  private void closeBackendImport(Backend<?> backend) throws DirectoryException
  {
    String lockFile = LockFileManager.getBackendLockFileName(backend);
    StringBuilder failureReason = new StringBuilder();
@@ -3785,7 +3783,7 @@
   * Returns the backend associated to this domain.
   * @return The associated backend.
   */
  private Backend getBackend()
  private Backend<?> getBackend()
  {
    return DirectoryServer.getBackend(getBaseDN());
  }
@@ -4071,29 +4069,6 @@
    super.sessionInitiated(initStatus, rsState);
    // Now that we are connected , we can enable ECL if :
    // 1/ RS must in the same JVM and created an ECL_WORKFLOW_ELEMENT
    // and 2/ this domain must NOT be private
    if (!getBackend().isPrivateBackend())
    {
      try
      {
        ECLWorkflowElement wfe = (ECLWorkflowElement)
        DirectoryServer.getWorkflowElement(
            ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
        if (wfe != null)
        {
          wfe.getReplicationServer().enableECL();
        }
      }
      catch (DirectoryException de)
      {
        logger.info(NOTE_ERR_UNABLE_TO_ENABLE_ECL,
            "Replication Domain on " + getBaseDNString(), stackTraceToSingleLineString(de));
        // and go on
      }
    }
    // Now for bad data set status if needed
    if (forceBadDataSet)
    {
@@ -4346,7 +4321,7 @@
  @Override
  public long countEntries() throws DirectoryException
  {
    Backend backend = getBackend();
    Backend<?> backend = getBackend();
    if (!backend.supportsLDIFExport())
    {
      LocalizableMessage msg = ERR_INIT_EXPORT_NOT_SUPPORTED.get(backend.getBackendID());
opendj3-server-dev/src/server/org/opends/server/replication/server/ECLServerHandler.java
@@ -34,6 +34,7 @@
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.backends.ChangelogBackend;
import org.opends.server.replication.common.CSN;
import org.opends.server.replication.common.MultiDomainServerState;
import org.opends.server.replication.common.ServerState;
@@ -155,8 +156,13 @@
    private final ReplicationServerDomain rsDomain;
    /**
     * Active when there are still changes supposed eligible for the ECL. It is
     * active by default.
     * Active when there are still changes supposed eligible for the ECL.
     * Here is the lifecycle of this field:
     * <ol>
     * <li>active==true at the start of the INIT phase,</li>
     * <li>active==false when there are no more changes for a domain in the the INIT phase,</li>
     * <li>active==true if it is a persistent search on external changelog. It never moves again</li>
     * </ol>
     */
    private boolean active = true;
    private UpdateMsg nextMsg;
@@ -354,8 +360,7 @@
    super(session, queueSize, replicationServer, rcvWindowSize);
    try
    {
      DN baseDN = DN.valueOf(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
      setBaseDNAndDomain(baseDN, true);
      setBaseDNAndDomain(ChangelogBackend.CHANGELOG_BASE_DN, true);
    }
    catch(DirectoryException de)
    {
@@ -853,14 +858,6 @@
  }
  /**
   * Registers this handler into its related domain and notifies the domain.
   */
  private void registerIntoDomain()
  {
    replicationServerDomain.registerHandler(this);
  }
  /**
   * Shutdown this handler.
   */
  @Override
@@ -871,14 +868,20 @@
      logger.trace(this + " shutdown()");
    }
    releaseCursor();
    for (DomainContext domainCtxt : domainCtxts) {
      if (!domainCtxt.unRegisterHandler()) {
        logger.error(LocalizableMessage.raw(this + " shutdown() - error when unregistering handler "+ domainCtxt.mh));
    if (domainCtxts != null)
    {
      for (DomainContext domainCtxt : domainCtxts)
      {
        if (!domainCtxt.unRegisterHandler())
        {
          logger.error(LocalizableMessage.raw(this + " shutdown() - error when unregistering handler " + domainCtxt.mh));
        }
        domainCtxt.stopServer();
      }
      domainCtxt.stopServer();
      domainCtxts = null;
    }
    super.shutdown();
    domainCtxts = null;
  }
  private void releaseCursor()
@@ -1020,11 +1023,11 @@
      closeInitPhase();
    }
    registerIntoDomain();
    replicationServerDomain.registerHandler(this);
    if (logger.isTraceEnabled())
    {
      logger.trace(getClass().getCanonicalName() + " " + getOperationId()
      logger.trace(getClass().getSimpleName() + " " + getOperationId()
          + " initialized: " + " " + dumpState() + domaimCtxtsToString(""));
    }
  }
@@ -1372,7 +1375,7 @@
          + dumpState());
    }
    // go to persistent phase if one
    // set all domains to be active again for the persistent phase
    for (DomainContext domainCtxt : domainCtxts) domainCtxt.active = true;
    if (startECLSessionMsg.getPersistent() != NON_PERSISTENT)
opendj3-server-dev/src/server/org/opends/server/replication/server/ECLServerWriter.java
@@ -29,7 +29,7 @@
import java.io.IOException;
import java.net.SocketException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.backends.ChangelogBackend;
import org.opends.server.core.PersistentSearch;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.replication.protocol.DoneMsg;
@@ -39,7 +39,6 @@
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.workflowelement.externalchangelog.ECLSearchOperation;
import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
import static org.opends.messages.ReplicationMessages.*;
import static org.opends.server.util.StaticUtils.*;
@@ -89,9 +88,8 @@
   */
  private PersistentSearch findPersistentSearch(ECLServerHandler handler)
  {
    ECLWorkflowElement wfe = (ECLWorkflowElement)
        DirectoryServer.getWorkflowElement(ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
    for (PersistentSearch psearch : wfe.getPersistentSearches())
    final ChangelogBackend backend = ChangelogBackend.getInstance();
    for (PersistentSearch psearch : backend.getPersistentSearches())
    {
      if (psearch.getSearchOperation().toString().equals(
          handler.getOperationId()))
opendj3-server-dev/src/server/org/opends/server/replication/server/ReplicationServer.java
@@ -31,7 +31,6 @@
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -46,8 +45,6 @@
import org.opends.server.api.VirtualAttributeProvider;
import org.opends.server.backends.ChangelogBackend;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.WorkflowImpl;
import org.opends.server.core.networkgroups.NetworkGroup;
import org.opends.server.replication.common.*;
import org.opends.server.replication.plugin.MultimasterReplication;
import org.opends.server.replication.protocol.*;
@@ -60,8 +57,6 @@
import org.opends.server.replication.server.changelog.je.JEChangelogDB;
import org.opends.server.replication.service.DSRSShutdownSync;
import org.opends.server.types.*;
import org.opends.server.util.ServerConstants;
import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.messages.ReplicationMessages.*;
@@ -109,12 +104,6 @@
  /** To know whether a domain is enabled for the external changelog. */
  private final ECLEnabledDomainPredicate domainPredicate;
  private static final String eclWorkflowID =
    "External Changelog Workflow ID";
  private ECLWorkflowElement eclwe;
  private final AtomicReference<WorkflowImpl> eclWorkflowImpl =
      new AtomicReference<WorkflowImpl>();
  /**
   * This is required for unit testing, so that we can keep track of all the
   * replication servers which are running in the VM.
@@ -173,6 +162,8 @@
    this.config = cfg;
    this.dsrsShutdownSync = dsrsShutdownSync;
    this.domainPredicate = predicate;
    enableExternalChangeLog();
    ReplicationDBImplementation dbImpl = cfg.getReplicationDBImplementation();
    logger.trace("Using %s as DB implementation for changelog DB", dbImpl);
    if (dbImpl == ReplicationDBImplementation.JE)
@@ -188,9 +179,6 @@
    initialize();
    cfg.addChangeListener(this);
    // TODO : uncomment to branch changelog backend
    //enableExternalChangeLog();
    localPorts.add(getReplicationPort());
    // Keep track of this new instance
@@ -458,15 +446,6 @@
      listenThread = new ReplicationServerListenThread(this);
      listenThread.start();
      // Creates the ECL workflow elem so that DS (LDAPReplicationDomain)
      // can know me and really enableECL.
      if (WorkflowImpl.getWorkflow(eclWorkflowID) != null)
      {
        // Already done. Nothing to do
        return;
      }
      eclwe = new ECLWorkflowElement(this);
      if (logger.isTraceEnabled())
      {
        logger.trace("RS " + getMonitorInstanceName() + " successfully initialized");
@@ -477,50 +456,10 @@
    } catch (IOException e)
    {
      logger.error(ERR_COULD_NOT_BIND_CHANGELOG, getReplicationPort(), e.getMessage());
    } catch (DirectoryException e)
    {
      //FIXME:DirectoryException is raised by initializeECL => fix err msg
      logger.error(LocalizableMessage.raw(
          "Directory Exception raised by ECL initialization: %s", e.getMessage()));
    }
  }
  /**
   * Enable the ECL access by creating a dedicated workflow element.
   * @throws DirectoryException when an error occurs.
   */
  public void enableECL() throws DirectoryException
  {
    if (eclWorkflowImpl.get() != null)
    {
      // ECL is already enabled, do nothing
      return;
    }
    // Create the workflow for the base DN
    // and register the workflow with the server.
    final DN dn = DN.valueOf(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
    final WorkflowImpl workflowImpl = new WorkflowImpl(eclWorkflowID, dn,
        eclwe.getWorkflowElementID(), eclwe);
    if (!eclWorkflowImpl.compareAndSet(null, workflowImpl))
    {
      // ECL is being enabled, do nothing
      return;
    }
    workflowImpl.register();
    NetworkGroup.getDefaultNetworkGroup().registerWorkflow(workflowImpl);
    // FIXME:ECL should the ECL Workflow be registered in admin and internal
    // network groups?
    NetworkGroup.getAdminNetworkGroup().registerWorkflow(workflowImpl);
    NetworkGroup.getInternalNetworkGroup().registerWorkflow(workflowImpl);
    registerVirtualAttributeRules();
  }
  /**
   * Enable the external changelog if it is not already enabled.
   * <p>
   * The external changelog is provided by the changelog backend.
@@ -636,34 +575,6 @@
    }
  }
  private void shutdownECL()
  {
    WorkflowImpl eclwf = (WorkflowImpl) WorkflowImpl.getWorkflow(eclWorkflowID);
    // do it only if not already done by another RS (unit test case)
    if (eclwf != null)
    {
      // FIXME:ECL should the ECL Workflow be registered in admin and internal
      // network groups?
      NetworkGroup.getInternalNetworkGroup().deregisterWorkflow(eclWorkflowID);
      NetworkGroup.getAdminNetworkGroup().deregisterWorkflow(eclWorkflowID);
      NetworkGroup.getDefaultNetworkGroup().deregisterWorkflow(eclWorkflowID);
      deregisterVirtualAttributeRules();
      eclwf.deregister();
      eclwf.finalizeWorkflow();
    }
    eclwe = (ECLWorkflowElement) DirectoryServer
        .getWorkflowElement("EXTERNAL CHANGE LOG");
    if (eclwe != null)
    {
      DirectoryServer.deregisterWorkflowElement(eclwe);
      eclwe.finalizeWorkflowElement();
    }
  }
  /**
   * Get the ReplicationServerDomain associated to the base DN given in
   * parameter.
@@ -901,9 +812,7 @@
      domain.shutdown();
    }
    // TODO : switch to second method when changelog backend is branched
    shutdownECL();
    //shutdownExternalChangelog();
    shutdownExternalChangelog();
    try
    {
opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/je/ChangeNumberIndexer.java
@@ -31,6 +31,7 @@
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.api.DirectoryThread;
import org.opends.server.backends.ChangelogBackend;
import org.opends.server.replication.common.CSN;
import org.opends.server.replication.common.MultiDomainServerState;
import org.opends.server.replication.common.ServerState;
@@ -524,6 +525,7 @@
  protected void notifyEntryAddedToChangelog(DN baseDN, long changeNumber,
      MultiDomainServerState cookie, UpdateMsg msg) throws ChangelogException
  {
    ChangelogBackend.getInstance().notifyEntryAdded(baseDN, changeNumber, cookie.toString(), msg);
  }
  /**
opendj3-server-dev/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java
@@ -39,6 +39,7 @@
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.admin.std.server.ReplicationServerCfg;
import org.opends.server.api.DirectoryThread;
import org.opends.server.backends.ChangelogBackend;
import org.opends.server.replication.common.CSN;
import org.opends.server.replication.common.MultiDomainServerState;
import org.opends.server.replication.common.ServerState;
@@ -838,6 +839,8 @@
    final JEReplicaDB replicaDB = pair.getFirst();
    replicaDB.add(updateMsg);
    ChangelogBackend.getInstance().notifyEntryAdded(baseDN, 0, null, updateMsg);
    final ChangeNumberIndexer indexer = cnIndexer.get();
    if (indexer != null)
    {
opendj3-server-dev/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
@@ -37,6 +37,7 @@
import org.forgerock.opendj.ldap.SearchScope;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.backends.ChangelogBackend;
import org.opends.server.config.ConfigConstants;
import org.opends.server.controls.*;
import org.opends.server.core.*;
@@ -62,6 +63,7 @@
import static org.opends.messages.CoreMessages.*;
import static org.opends.messages.ReplicationMessages.*;
import static org.opends.server.backends.ChangelogBackend.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.replication.protocol.StartECLSessionMsg.ECLRequestType.*;
import static org.opends.server.replication.protocol.StartECLSessionMsg.Persistent.*;
@@ -120,22 +122,6 @@
  private static final AttributeType MODIFIERS_NAME_TYPE =
      DirectoryConfig.getAttributeType(OP_ATTR_MODIFIERS_NAME_LC, true);
  /** The associated DN. */
  private static final DN CHANGELOG_ROOT_DN;
  static
  {
    try
    {
      CHANGELOG_ROOT_DN = DN
          .valueOf(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
    }
    catch (Exception e)
    {
      throw new RuntimeException(e);
    }
  }
  /**
   * The replication server in which the search on ECL is to be performed.
   */
@@ -298,7 +284,10 @@
      // If there's a persistent search, then register it with the server.
      if (persistentSearch != null)
      {
        wfe.registerPersistentSearch(persistentSearch);
        ChangelogBackend.getInstance().registerPersistentSearch(persistentSearch);
        // TODO JNR Add callback on cancel,
        // see ECLWorkflowElement.registerPersistentSearch().
        // This will be removed very soon anyway.
        persistentSearch.enable();
      }
@@ -518,6 +507,7 @@
          persistentSearch = new PersistentSearch(this,
              psearchControl.getChangeTypes(),
              psearchControl.getChangesOnly(),
              psearchControl.getReturnECs());
          // If we're only interested in changes, then we don't actually want
@@ -596,7 +586,7 @@
      ECLUpdateMsg update = eclServerHandler.getNextECLUpdate();
      // Return root entry if requested.
      if (CHANGELOG_ROOT_DN.matchesBaseAndScope(baseDN, getScope()))
      if (CHANGELOG_BASE_DN.matchesBaseAndScope(baseDN, getScope()))
      {
        final Entry entry = createRootEntry(update != null);
        if (filter.matchesEntry(entry) && !returnEntry(entry, null))
@@ -607,7 +597,7 @@
        }
      }
      if (baseDN.equals(CHANGELOG_ROOT_DN)
      if (baseDN.equals(CHANGELOG_BASE_DN)
          && getScope().equals(SearchScope.BASE_OBJECT))
      {
        // Only the change log root entry was requested. There is no need to
@@ -907,9 +897,9 @@
    addAttributeByUppercaseName("hassubordinates", "hasSubordinates",
        Boolean.toString(hasSubordinates), userAttrs, operationalAttrs);
    addAttributeByUppercaseName("entrydn", "entryDN",
        CHANGELOG_ROOT_DN.toNormalizedString(), userAttrs, operationalAttrs);
        DN_EXTERNAL_CHANGELOG_ROOT, userAttrs, operationalAttrs);
    return new Entry(CHANGELOG_ROOT_DN, CHANGELOG_ROOT_OBJECT_CLASSES,
    return new Entry(CHANGELOG_BASE_DN, CHANGELOG_ROOT_OBJECT_CLASSES,
        userAttrs, operationalAttrs);
  }
opendj3-server-dev/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
@@ -26,15 +26,11 @@
 */
package org.opends.server.workflowelement.externalchangelog;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.opends.server.admin.std.server.WorkflowElementCfg;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.SearchOperation;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.replication.server.ReplicationServer;
@@ -43,25 +39,15 @@
import org.opends.server.types.Operation;
import org.opends.server.workflowelement.LeafWorkflowElement;
/**
 * This class defines a workflow element for the external changelog (ECL);
 * e-g an entity that handles the processing of an operation against the ECL.
 */
public class ECLWorkflowElement extends
    LeafWorkflowElement<WorkflowElementCfg>
public class ECLWorkflowElement extends LeafWorkflowElement<WorkflowElementCfg>
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /**
   *The set of persistent searches registered with this work flow element.
   */
  private final List<PersistentSearch> persistentSearches =
    new CopyOnWriteArrayList<PersistentSearch>();
  /**
   * A string indicating the type of the workflow element.
   */
  public static final String ECL_WORKFLOW_ELEMENT = "EXTERNAL CHANGE LOG";
@@ -70,7 +56,7 @@
   * The replication server object to which we will submits request
   * on the ECL. Retrieved from the local DirectoryServer.
   */
  private ReplicationServer replicationServer;
  private final ReplicationServer replicationServer;
  /**
   * Creates a new instance of the External Change Log workflow element.
@@ -86,26 +72,16 @@
    DirectoryServer.registerWorkflowElement(this);
  }
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public void finalizeWorkflowElement()
  {
    // null all fields so that any use of the finalized object will raise
    // an NPE
    // null all fields so that any use of the finalized object will raise a NPE
    super.initialize(ECL_WORKFLOW_ELEMENT, null);
    // Cancel all persistent searches.
    for (PersistentSearch psearch : persistentSearches) {
      psearch.cancel();
    }
    persistentSearches.clear();
  }
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public void execute(Operation operation) throws CanceledOperationException {
    switch (operation.getOperationType())
    {
@@ -166,45 +142,6 @@
  }
  /**
   * Registers the provided persistent search operation with this
   * workflow element so that it will be notified of any
   * add, delete, modify, or modify DN operations that are performed.
   *
   * @param persistentSearch
   *          The persistent search operation to register with this
   *          workflow element.
   */
  void registerPersistentSearch(PersistentSearch persistentSearch)
  {
    PersistentSearch.CancellationCallback callback =
      new PersistentSearch.CancellationCallback()
    {
      public void persistentSearchCancelled(PersistentSearch psearch)
      {
        psearch.getSearchOperation().cancel(null);
        persistentSearches.remove(psearch);
      }
    };
    persistentSearches.add(persistentSearch);
    persistentSearch.registerCancellationCallback(callback);
  }
  /**
   * Gets the list of persistent searches currently active against
   * this workflow element.
   *
   * @return The list of persistent searches currently active against
   *         this workflow element.
   */
  public List<PersistentSearch> getPersistentSearches()
  {
    return persistentSearches;
  }
  /**
   * Returns the associated replication server.
   * @return the rs.
   */
opendj3-server-dev/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -215,7 +215,7 @@
        @Override
        public void run()
        {
          for (PersistentSearch psearch : wfe.getPersistentSearches())
          for (PersistentSearch psearch : backend.getPersistentSearches())
          {
            psearch.processAdd(entry);
          }
opendj3-server-dev/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -186,7 +186,7 @@
        @Override
        public void run()
        {
          for (PersistentSearch psearch : wfe.getPersistentSearches())
          for (PersistentSearch psearch : backend.getPersistentSearches())
          {
            psearch.processDelete(entry);
          }
opendj3-server-dev/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -225,7 +225,7 @@
        @Override
        public void run()
        {
          for (PersistentSearch psearch : wfe.getPersistentSearches())
          for (PersistentSearch psearch : backend.getPersistentSearches())
          {
            psearch.processModifyDN(newEntry, currentEntry.getName());
          }
opendj3-server-dev/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -355,7 +355,7 @@
        @Override
        public void run()
        {
          for (PersistentSearch psearch : wfe.getPersistentSearches())
          for (PersistentSearch psearch : backend.getPersistentSearches())
          {
            psearch.processModify(modifiedEntry, currentEntry);
          }
@@ -666,7 +666,7 @@
        Control c = iter.next();
        String  oid = c.getOID();
        if (oid.equals(OID_LDAP_ASSERTION))
        if (OID_LDAP_ASSERTION.equals(oid))
        {
          LDAPAssertionRequestControl assertControl =
                getRequestControl(LDAPAssertionRequestControl.DECODER);
@@ -718,19 +718,19 @@
                    entryDN, de.getMessageObject()));
          }
        }
        else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
        else if (OID_LDAP_NOOP_OPENLDAP_ASSIGNED.equals(oid))
        {
          noOp = true;
        }
        else if (oid.equals(OID_PERMISSIVE_MODIFY_CONTROL))
        else if (OID_PERMISSIVE_MODIFY_CONTROL.equals(oid))
        {
          permissiveModify = true;
        }
        else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
        else if (OID_LDAP_READENTRY_PREREAD.equals(oid))
        {
          preReadRequest = getRequestControl(LDAPPreReadRequestControl.DECODER);
        }
        else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
        else if (OID_LDAP_READENTRY_POSTREAD.equals(oid))
        {
          if (c instanceof LDAPPostReadRequestControl)
          {
@@ -742,7 +742,7 @@
            iter.set(postReadRequest);
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V1))
        else if (OID_PROXIED_AUTH_V1.equals(oid))
        {
          // Log usage of legacy proxy authz V1 control.
          addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
@@ -763,7 +763,7 @@
          setAuthorizationEntry(authorizationEntry);
          setProxiedAuthorizationDN(getName(authorizationEntry));
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        else if (OID_PROXIED_AUTH_V2.equals(oid))
        {
          // The requester must have the PROXIED_AUTH privilege in order to
          // be able to use this control.
@@ -780,7 +780,7 @@
          setAuthorizationEntry(authorizationEntry);
          setProxiedAuthorizationDN(getName(authorizationEntry));
        }
        else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
        else if (OID_PASSWORD_POLICY_CONTROL.equals(oid))
        {
          pwPolicyControlRequested = true;
        }
@@ -846,13 +846,11 @@
      // See if the attribute is one which controls the privileges available for
      // a user.  If it is, then the client must have the PRIVILEGE_CHANGE
      // privilege.
      if (t.hasName(OP_ATTR_PRIVILEGE_NAME))
      if (t.hasName(OP_ATTR_PRIVILEGE_NAME)
          && !clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this))
      {
        if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this))
        {
          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                  ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
        }
        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
      }
      // If the modification is not updating the password attribute,
@@ -1086,11 +1084,11 @@
      numPasswords = passwordsToAdd;
    }
    // If there were multiple password values, then make sure that's
    // OK.
    if ((!isInternalOperation())
        && (!pwPolicyState.getAuthenticationPolicy()
            .isAllowMultiplePasswordValues()) && (passwordsToAdd > 1))
    // If there were multiple password values, then make sure that's OK.
    final PasswordPolicy authPolicy = pwPolicyState.getAuthenticationPolicy();
    if (!isInternalOperation()
        && !authPolicy.isAllowMultiplePasswordValues()
        && passwordsToAdd > 1)
    {
      pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
@@ -1106,9 +1104,8 @@
    {
      if (pwPolicyState.passwordIsPreEncoded(v))
      {
        if ((!isInternalOperation())
            && !pwPolicyState.getAuthenticationPolicy()
                .isAllowPreEncodedPasswords())
        if (!isInternalOperation()
            && !authPolicy.isAllowPreEncodedPasswords())
        {
          pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
@@ -1121,15 +1118,13 @@
      }
      else
      {
        if (m.getModificationType() == ModificationType.ADD)
        if (m.getModificationType() == ModificationType.ADD
            // Make sure that the password value does not already exist.
            && pwPolicyState.passwordMatches(v))
        {
          // Make sure that the password value doesn't already exist.
          if (pwPolicyState.passwordMatches(v))
          {
            pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
            throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
                ERR_MODIFY_PASSWORD_EXISTS.get());
          }
          pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
          throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
              ERR_MODIFY_PASSWORD_EXISTS.get());
        }
        if (newPasswords == null)
@@ -1214,7 +1209,7 @@
      else
      {
        List<Attribute> attrList = currentEntry.getAttribute(pwAttr.getAttributeType());
        if ((attrList == null) || (attrList.isEmpty()))
        if (attrList == null || attrList.isEmpty())
        {
          throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
              ERR_MODIFY_NO_EXISTING_VALUES.get());
@@ -1232,48 +1227,37 @@
                    .decodeAuthPassword(av.toString());
                PasswordStorageScheme<?> scheme = DirectoryServer
                    .getAuthPasswordStorageScheme(components[0].toString());
                if (scheme != null)
                {
                  if (scheme.authPasswordMatches(v,
                      components[1].toString(), components[2].toString()))
                  {
                    builder.add(av);
                    found = true;
                  }
                }
              }
              else
              {
                if (av.equals(v))
                {
                  builder.add(v);
                  found = true;
                }
              }
            }
            else
            {
              if (UserPasswordSyntax.isEncoded(av))
              {
                String[] components = UserPasswordSyntax.decodeUserPassword(av.toString());
                PasswordStorageScheme<?> scheme = DirectoryServer
                    .getPasswordStorageScheme(toLowerCase(components[0]));
                if (scheme != null
                    && scheme.passwordMatches(v, ByteString.valueOf(components[1])))
                    && scheme.authPasswordMatches(v,
                        components[1].toString(), components[2].toString()))
                {
                  builder.add(av);
                  found = true;
                }
              }
              else
              else if (av.equals(v))
              {
                if (av.equals(v))
                {
                  builder.add(v);
                  found = true;
                }
                builder.add(v);
                found = true;
              }
            }
            else if (UserPasswordSyntax.isEncoded(av))
            {
              String[] components = UserPasswordSyntax.decodeUserPassword(av.toString());
              PasswordStorageScheme<?> scheme = DirectoryServer
                  .getPasswordStorageScheme(toLowerCase(components[0]));
              if (scheme != null
                  && scheme.passwordMatches(v, ByteString.valueOf(components[1])))
              {
                builder.add(av);
                found = true;
              }
            }
            else if (av.equals(v))
            {
              builder.add(v);
              found = true;
            }
          }
        }
@@ -1706,13 +1690,8 @@
  public void performAdditionalPasswordChangedProcessing()
         throws DirectoryException
  {
    if (pwPolicyState == null)
    {
      // Account not managed locally so nothing to do.
      return;
    }
    if (!passwordChanged)
    if (!passwordChanged
        || pwPolicyState == null) // Account not managed locally
    {
      // Nothing to do.
      return;
@@ -1743,70 +1722,62 @@
    // If any of the password values should be validated, then do so now.
    if (selfChange || !authPolicy.isSkipValidationForAdministrators())
    if (newPasswords != null
        && (selfChange || !authPolicy.isSkipValidationForAdministrators()))
    {
      if (newPasswords != null)
      HashSet<ByteString> clearPasswords = new HashSet<ByteString>(pwPolicyState.getClearPasswords());
      if (currentPasswords != null)
      {
        HashSet<ByteString> clearPasswords = new HashSet<ByteString>();
        clearPasswords.addAll(pwPolicyState.getClearPasswords());
        if (currentPasswords != null)
        if (clearPasswords.isEmpty())
        {
          if (clearPasswords.isEmpty())
          clearPasswords.addAll(currentPasswords);
        }
        else
        {
          // NOTE:  We can't rely on the fact that Set doesn't allow
          // duplicates because technically it's possible that the values
          // aren't duplicates if they are ASN.1 elements with different types
          // (like 0x04 for a standard universal octet string type versus 0x80
          // for a simple password in a bind operation).  So we have to
          // manually check for duplicates.
          for (ByteString pw : currentPasswords)
          {
            clearPasswords.addAll(currentPasswords);
          }
          else
          {
            // NOTE:  We can't rely on the fact that Set doesn't allow
            // duplicates because technically it's possible that the values
            // aren't duplicates if they are ASN.1 elements with different types
            // (like 0x04 for a standard universal octet string type versus 0x80
            // for a simple password in a bind operation).  So we have to
            // manually check for duplicates.
            for (ByteString pw : currentPasswords)
            if (!clearPasswords.contains(pw))
            {
              if (!clearPasswords.contains(pw))
              {
                clearPasswords.add(pw);
              }
              clearPasswords.add(pw);
            }
          }
        }
      }
        for (ByteString v : newPasswords)
      for (ByteString v : newPasswords)
      {
        LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder();
        if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
                                 v, clearPasswords, invalidReason))
        {
          LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder();
          if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
                                   v, clearPasswords, invalidReason))
          {
            pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                ERR_MODIFY_PW_VALIDATION_FAILED.get(invalidReason));
          }
          pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
              ERR_MODIFY_PW_VALIDATION_FAILED.get(invalidReason));
        }
      }
    }
    // If we should check the password history, then do so now.
    if (pwPolicyState.maintainHistory())
    if (newPasswords != null && pwPolicyState.maintainHistory())
    {
      if (newPasswords != null)
      for (ByteString v : newPasswords)
      {
        for (ByteString v : newPasswords)
        if (pwPolicyState.isPasswordInHistory(v)
            && (selfChange || !authPolicy.isSkipValidationForAdministrators()))
        {
          if (pwPolicyState.isPasswordInHistory(v)
              && (selfChange || !authPolicy.isSkipValidationForAdministrators()))
          {
            pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                ERR_MODIFY_PW_IN_HISTORY.get());
          }
          pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
              ERR_MODIFY_PW_IN_HISTORY.get());
        }
        pwPolicyState.updatePasswordHistory();
      }
      pwPolicyState.updatePasswordHistory();
    }
@@ -1862,7 +1833,7 @@
      return;
    }
    if (!(passwordChanged || enabledStateChanged || wasLocked))
    if (!passwordChanged && !enabledStateChanged && !wasLocked)
    {
      // Account managed locally, but unchanged, so nothing to do.
      return;
opendj3-server-dev/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
@@ -56,41 +56,21 @@
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /** The backend in which the search is to be performed. */
  private Backend<?> backend;
  /**
   * The backend in which the search is to be performed.
   */
  private Backend backend;
  /**
   * Indicates whether we should actually process the search.  This should
   * only be false if it's a persistent search with changesOnly=true.
   */
  private boolean processSearch;
  /**
   * The client connection for the search operation.
   */
  /** The client connection for the search operation. */
  private ClientConnection clientConnection;
  /**
   * The base DN for the search.
   */
  /** The base DN for the search. */
  private DN baseDN;
  /**
   * The persistent search request, if applicable.
   */
  /** The persistent search request, if applicable. */
  private PersistentSearch persistentSearch;
  /**
   * The filter for the search.
   */
  /** The filter for the search. */
  private SearchFilter filter;
  /**
   * Creates a new operation that may be used to search for entries in a local
   * backend of the Directory Server.
@@ -117,10 +97,7 @@
      throws CanceledOperationException
  {
    this.backend = wfe.getBackend();
    clientConnection = getClientConnection();
    processSearch = true;
    this.clientConnection = getClientConnection();
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
@@ -128,7 +105,7 @@
    try
    {
      BooleanHolder executePostOpPlugins = new BooleanHolder(false);
      processSearch(wfe, executePostOpPlugins);
      processSearch(executePostOpPlugins);
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
@@ -154,8 +131,7 @@
    }
  }
  private void processSearch(LocalBackendWorkflowElement wfe,
      BooleanHolder executePostOpPlugins) throws CanceledOperationException
  private void processSearch(BooleanHolder executePostOpPlugins) throws CanceledOperationException
  {
    // Process the search base and filter to convert them from their raw forms
    // as provided by the client to the forms required for the rest of the
@@ -163,7 +139,7 @@
    baseDN = getBaseDN();
    filter = getFilter();
    if ((baseDN == null) || (filter == null))
    if (baseDN == null || filter == null)
    {
      return;
    }
@@ -245,8 +221,13 @@
    // If there's a persistent search, then register it with the server.
    boolean processSearchNow = true;
    if (persistentSearch != null)
    {
      // If we're only interested in changes, then we do not actually want
      // to process the search now.
      processSearchNow = !persistentSearch.isChangesOnly();
      // The Core server maintains the count of concurrent persistent searches
      // so that all the backends (Remote and Local) are aware of it. Verify
      // with the core if we have already reached the threshold.
@@ -256,7 +237,7 @@
        appendErrorMessage(ERR_MAX_PSEARCH_LIMIT_EXCEEDED.get());
        return;
      }
      wfe.registerPersistentSearch(persistentSearch);
      backend.registerPersistentSearch(persistentSearch);
      persistentSearch.enable();
    }
@@ -264,7 +245,7 @@
    // Process the search in the backend and all its subordinates.
    try
    {
      if (processSearch)
      if (processSearchNow)
      {
        backend.search(this);
      }
@@ -320,14 +301,13 @@
    LocalBackendWorkflowElement.removeAllDisallowedControls(baseDN, this);
    List<Control> requestControls  = getRequestControls();
    if ((requestControls != null) && (! requestControls.isEmpty()))
    if (requestControls != null && ! requestControls.isEmpty())
    {
      for (int i=0; i < requestControls.size(); i++)
      for (Control c : requestControls)
      {
        Control c   = requestControls.get(i);
        String  oid = c.getOID();
        if (oid.equals(OID_LDAP_ASSERTION))
        if (OID_LDAP_ASSERTION.equals(oid))
        {
          LDAPAssertionRequestControl assertControl =
                getRequestControl(LDAPAssertionRequestControl.DECODER);
@@ -397,7 +377,7 @@
                                de.getMessageObject()), de);
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V1))
        else if (OID_PROXIED_AUTH_V1.equals(oid))
        {
          // Log usage of legacy proxy authz V1 control.
          addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
@@ -416,16 +396,9 @@
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.rootDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getName());
          }
          setProxiedAuthorizationDN(getName(authorizationEntry));
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        else if (OID_PROXIED_AUTH_V2.equals(oid))
        {
          // The requester must have the PROXIED_AUTH privilege in order to be
          // able to use this control.
@@ -440,38 +413,23 @@
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.rootDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getName());
          }
          setProxiedAuthorizationDN(getName(authorizationEntry));
        }
        else if (oid.equals(OID_PERSISTENT_SEARCH))
        else if (OID_PERSISTENT_SEARCH.equals(oid))
        {
          PersistentSearchControl psearchControl =
            getRequestControl(PersistentSearchControl.DECODER);
          final PersistentSearchControl ctrl =
              getRequestControl(PersistentSearchControl.DECODER);
          persistentSearch = new PersistentSearch(this,
                                      psearchControl.getChangeTypes(),
                                      psearchControl.getReturnECs());
          // If we're only interested in changes, then we don't actually want
          // to process the search now.
          if (psearchControl.getChangesOnly())
          {
            processSearch = false;
          }
              ctrl.getChangeTypes(), ctrl.getChangesOnly(), ctrl.getReturnECs());
        }
        else if (oid.equals(OID_LDAP_SUBENTRIES))
        else if (OID_LDAP_SUBENTRIES.equals(oid))
        {
          SubentriesControl subentriesControl =
                  getRequestControl(SubentriesControl.DECODER);
          setReturnSubentriesOnly(subentriesControl.getVisibility());
        }
        else if (oid.equals(OID_LDUP_SUBENTRIES))
        else if (OID_LDUP_SUBENTRIES.equals(oid))
        {
          // Support for legacy draft-ietf-ldup-subentry.
          addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
@@ -479,25 +437,25 @@
          setReturnSubentriesOnly(true);
        }
        else if (oid.equals(OID_MATCHED_VALUES))
        else if (OID_MATCHED_VALUES.equals(oid))
        {
          MatchedValuesControl matchedValuesControl =
                getRequestControl(MatchedValuesControl.DECODER);
          setMatchedValuesControl(matchedValuesControl);
        }
        else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
        else if (OID_ACCOUNT_USABLE_CONTROL.equals(oid))
        {
          setIncludeUsableControl(true);
        }
        else if (oid.equals(OID_REAL_ATTRS_ONLY))
        else if (OID_REAL_ATTRS_ONLY.equals(oid))
        {
          setRealAttributesOnly(true);
        }
        else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
        else if (OID_VIRTUAL_ATTRS_ONLY.equals(oid))
        {
          setVirtualAttributesOnly(true);
        }
        else if (oid.equals(OID_GET_EFFECTIVE_RIGHTS) &&
        else if (OID_GET_EFFECTIVE_RIGHTS.equals(oid) &&
          DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS))
        {
          // Do nothing here and let AciHandler deal with it.
@@ -514,6 +472,11 @@
    }
  }
  private DN getName(Entry e)
  {
    return e != null ? e.getName() : DN.rootDN();
  }
  /** Indicates if the backend supports the control corresponding to provided oid. */
  private boolean backendSupportsControl(final String oid)
  {
opendj3-server-dev/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -30,7 +30,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageDescriptor;
@@ -65,7 +64,7 @@
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /** the backend associated with the local workflow element. */
  private Backend backend;
  private Backend<?> backend;
  /** the set of local backend workflow elements registered with the server. */
@@ -74,13 +73,7 @@
            new TreeMap<String, LocalBackendWorkflowElement>();
  /**
   * The set of persistent searches registered with this work flow element.
   */
  private final List<PersistentSearch> persistentSearches =
    new CopyOnWriteArrayList<PersistentSearch>();
  /**
   * a lock to guarantee safe concurrent access to the registeredLocalBackends
   * A lock to guarantee safe concurrent access to the registeredLocalBackends
   * variable.
   */
  private static final Object registeredLocalBackendsLock = new Object();
@@ -109,9 +102,8 @@
   * @param workflowElementID  the workflow element identifier
   * @param backend  the backend associated to that workflow element
   */
  private void initialize(String workflowElementID, Backend backend)
  private void initialize(String workflowElementID, Backend<?> backend)
  {
    // Initialize the workflow ID
    super.initialize(workflowElementID, BACKEND_WORKFLOW_ELEMENT);
    this.backend  = backend;
@@ -151,29 +143,16 @@
    processWorkflowElementConfig(configuration, true);
  }
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public void finalizeWorkflowElement()
  {
    // null all fields so that any use of the finalized object will raise
    // an NPE
    // null all fields so that any use of the finalized object will raise a NPE
    super.initialize(null, null);
    backend = null;
    // Cancel all persistent searches.
    for (PersistentSearch psearch : persistentSearches) {
      psearch.cancel();
    }
    persistentSearches.clear();
  }
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public boolean isConfigurationChangeAcceptable(
      LocalBackendWorkflowElementCfg configuration,
@@ -183,10 +162,7 @@
    return processWorkflowElementConfig(configuration, false);
  }
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public ConfigChangeResult applyConfigurationChange(
      LocalBackendWorkflowElementCfg configuration
@@ -221,7 +197,7 @@
    {
      // Read configuration.
      String newBackendID = configuration.getBackend();
      Backend newBackend  = DirectoryServer.getBackend(newBackendID);
      Backend<?> newBackend = DirectoryServer.getBackend(newBackendID);
      // If the backend is null (i.e. not found in the list of
      // registered backends, this is probably because we are looking
@@ -270,8 +246,7 @@
   *         element.
   */
  public static LocalBackendWorkflowElement createAndRegister(
      String workflowElementID,
      Backend backend)
      String workflowElementID, Backend<?> backend)
  {
    // If the requested workflow element does not exist then create one.
    LocalBackendWorkflowElement localBackend =
@@ -655,11 +630,7 @@
    }
  }
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public void execute(Operation operation) throws CanceledOperationException {
    switch (operation.getOperationType())
@@ -760,54 +731,11 @@
   * @return The backend associated with this local backend workflow
   *         element.
   */
  public Backend getBackend()
  public Backend<?> getBackend()
  {
    return backend;
  }
  /**
   * Registers the provided persistent search operation with this
   * local backend workflow element so that it will be notified of any
   * add, delete, modify, or modify DN operations that are performed.
   *
   * @param persistentSearch
   *          The persistent search operation to register with this
   *          local backend workflow element.
   */
  void registerPersistentSearch(PersistentSearch persistentSearch)
  {
    PersistentSearch.CancellationCallback callback =
      new PersistentSearch.CancellationCallback()
    {
      @Override
      public void persistentSearchCancelled(PersistentSearch psearch)
      {
        persistentSearches.remove(psearch);
      }
    };
    persistentSearches.add(persistentSearch);
    persistentSearch.registerCancellationCallback(callback);
  }
  /**
   * Gets the list of persistent searches currently active against
   * this local backend workflow element.
   *
   * @return The list of persistent searches currently active against
   *         this local backend workflow element.
   */
  List<PersistentSearch> getPersistentSearches()
  {
    return persistentSearches;
  }
  /**
   * Checks if an update operation can be performed against a backend. The
   * operation will be rejected based on the server and backend writability
@@ -828,7 +756,7 @@
   * @throws DirectoryException
   *           If the update operation has been rejected.
   */
  static void checkIfBackendIsWritable(Backend backend, Operation op,
  static void checkIfBackendIsWritable(Backend<?> backend, Operation op,
      DN entryDN, LocalizableMessageDescriptor.Arg1<Object> serverMsg,
      LocalizableMessageDescriptor.Arg1<Object> backendMsg)
      throws DirectoryException
@@ -864,5 +792,14 @@
      }
    }
  }
}
  /** {@inheritDoc} */
  @Override
  public String toString()
  {
    return getClass().getSimpleName()
        + " backend=" + backend
        + " workflowElementID=" + getWorkflowElementID()
        + " workflowElementTypeInfo=" + getWorkflowElementTypeInfo();
  }
}
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ExternalChangeLogTest.java
@@ -29,6 +29,7 @@
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.*;
@@ -72,7 +73,6 @@
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.TimeThread;
import org.opends.server.workflowelement.externalchangelog.ECLSearchOperation;
import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
import org.opends.server.workflowelement.localbackend.LocalBackendModifyDNOperation;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
@@ -85,6 +85,7 @@
import static org.opends.server.TestCaseUtils.*;
import static org.opends.server.controls.PersistentSearchChangeType.*;
import static org.opends.server.replication.protocol.OperationContext.*;
import static org.opends.server.util.CollectionUtils.*;
import static org.opends.server.util.StaticUtils.*;
import static org.testng.Assert.*;
@@ -135,7 +136,7 @@
   * When used in a search operation, it includes all attributes (user and
   * operational)
   */
  private static final Set<String> ALL_ATTRIBUTES = newSet("*", "+");
  private static final Set<String> ALL_ATTRIBUTES = newHashSet("*", "+");
  private static final List<Control> NO_CONTROL = null;
  /**
@@ -178,14 +179,6 @@
  public void PrimaryTest() throws Exception
  {
    replicationServer.getChangelogDB().setPurgeDelay(0);
    // let's enable ECl manually now that we tested that ECl is not available
    ECLWorkflowElement wfe =
        (ECLWorkflowElement) DirectoryServer
        .getWorkflowElement(ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
    if (wfe != null)
    {
      wfe.getReplicationServer().enableECL();
    }
    // Test all types of ops.
    ECLAllOps(); // Do not clean the db for the next test
@@ -352,7 +345,7 @@
  /**
   * Verifies that is not possible to read the changelog without the changelog-read privilege
   */
  @Test(enabled=true, dependsOnMethods = { "PrimaryTest"})
  @Test(enabled = false, dependsOnMethods = { "PrimaryTest" })
  public void ECLChangelogReadPrivilegeTest() throws Exception
  {
    AuthenticationInfo nonPrivilegedUser = new AuthenticationInfo();
@@ -368,7 +361,22 @@
  @Test(enabled = true)
  public void TestECLIsNotASupportedSuffix() throws Exception
  {
    ECLCompatTestLimits(0,0, false);
    try
    {
      invoke(replicationServer, "shutdownExternalChangelog");
      ECLCompatTestLimits(0, 0, false);
    }
    finally
    {
      invoke(replicationServer, "enableExternalChangeLog");
    }
  }
  private void invoke(Object obj, String methodName) throws Exception
  {
    final Method m = obj.getClass().getDeclaredMethod(methodName);
    m.setAccessible(true);
    m.invoke(obj);
  }
  /**
@@ -561,7 +569,7 @@
    ReplicationBroker server01 = null;
    LDAPReplicationDomain domain = null;
    LDAPReplicationDomain domain2 = null;
    Backend backend2 = null;
    Backend<?> backend2 = null;
    // Use different values than other tests to avoid test interactions in concurrent test runs
    final String backendId2 = tn + 2;
@@ -642,7 +650,7 @@
    ReplicationBroker s2test = null;
    ReplicationBroker s2test2 = null;
    Backend backend2 = null;
    Backend<?> backend2 = null;
    LDAPReplicationDomain domain1 = null;
    LDAPReplicationDomain domain2 = null;
    try
@@ -954,7 +962,7 @@
  }
  /** Test ECL content after a domain has been removed. */
  @Test(enabled=true, dependsOnMethods = { "PrimaryTest"})
  @Test(enabled = false, dependsOnMethods = { "PrimaryTest" })
  public void testECLAfterDomainIsRemoved() throws Exception
  {
    String testName = "testECLAfterDomainIsRemoved";
@@ -1051,9 +1059,8 @@
    String cookie = "";
    LDIFWriter ldifWriter = getLDIFWriter();
    Set<String> lastcookieattribute = newSet("lastExternalChangelogCookie");
    InternalSearchOperation searchOp = searchOnRootDSE(lastcookieattribute);
    List<SearchResultEntry> entries = searchOp.getSearchEntries();
    final Set<String> attrs = newHashSet("lastExternalChangelogCookie");
    List<SearchResultEntry> entries = searchOnRootDSE(attrs).getSearchEntries();
    if (entries != null)
    {
      for (SearchResultEntry resultEntry : entries)
@@ -1155,7 +1162,7 @@
        checkValue(resultEntry, "replicationcsn", csns[i - 1].toString());
        checkValue(resultEntry, "replicaidentifier", String.valueOf(SERVER_ID_1));
        checkValue(resultEntry, "changelogcookie", cookies[i - 1]);
        checkValue(resultEntry, "changenumber", "0");
        assertNull(getAttributeValue(resultEntry, "changenumber"));
        if (i==1)
        {
@@ -1347,8 +1354,7 @@
    return a.iterator().next().toString();
  }
  private static void checkValues(Entry entry, String attrName,
      Set<String> expectedValues)
  private static void checkValues(Entry entry, String attrName, Set<String> expectedValues)
  {
    final Set<String> values = new HashSet<String>();
    for (Attribute a : entry.getAttribute(attrName))
@@ -1936,7 +1942,7 @@
  /**
   * Utility - create a second backend in order to test ECL with 2 suffixes.
   */
  private static Backend initializeTestBackend(boolean createBaseEntry,
  private static Backend<?> initializeTestBackend(boolean createBaseEntry,
      String backendId) throws Exception
  {
    DN baseDN = DN.valueOf("o=" + backendId);
@@ -1968,9 +1974,9 @@
    return memoryBackend;
  }
  private static void removeTestBackend(Backend... backends)
  private static void removeTestBackend(Backend<?>... backends)
  {
    for (Backend backend : backends)
    for (Backend<?> backend : backends)
    {
      if (backend != null)
      {
@@ -1994,7 +2000,7 @@
    ReplicationBroker s2test = null;
    ReplicationBroker s1test2 = null;
    ReplicationBroker s2test2 = null;
    Backend backend2 = null;
    Backend<?> backend2 = null;
    try
    {
@@ -2439,7 +2445,7 @@
    // available in other entries. We u
    debugInfo(tn, "Starting test \n\n");
    Set<String> attributes = newSet("firstchangenumber", "lastchangenumber",
    Set<String> attributes = newHashSet("firstchangenumber", "lastchangenumber",
        "changelog", "lastExternalChangelogCookie");
    debugInfo(tn, " Search: " + TEST_ROOT_DN_STRING);
@@ -2608,8 +2614,8 @@
    final String backendId3 = "test3";
    final DN baseDN3 = DN.valueOf("o=" + backendId3);
    Backend backend2 = null;
    Backend backend3 = null;
    Backend<?> backend2 = null;
    Backend<?> backend3 = null;
    LDAPReplicationDomain domain2 = null;
    LDAPReplicationDomain domain3 = null;
    LDAPReplicationDomain domain21 = null;
@@ -2707,7 +2713,7 @@
        {
          Entry targetEntry = parseIncludedAttributes(resultEntry, targetdn);
          Set<String> eoc = newSet("person", "inetOrgPerson", "organizationalPerson", "top");
          Set<String> eoc = newHashSet("person", "inetOrgPerson", "organizationalPerson", "top");
          checkValues(targetEntry, "objectclass", eoc);
          String changeType = getAttributeValue(resultEntry, "changetype");