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

matthew_swift
10.41.2008 218b40d6e175f5b58b89ff7e0b3050577d3aff2f
This change fixes issue 3567:

* Persistent searches are now registered in the following two places:

o the client connection associated with the search (i.e. as
before)

o per-WFE lists: it is now the responsibility of the WFE to
maintain this list.

* The PersistentSearch API has changed:

o it no longer depends on Operations for update notifications.
This means that the notifications can be sent from "out of
band" event sources, such as remote persistent searches
issued from a proxy WFE

o it now supports a cancel() method which should *always* be
used to cancel a persistent search. This method takes care
of all resource cleanup: deregistering from the client
connection and any WFE related resources

o in now provides a mechanism for registering callbacks which
should be invoked when the psearch is cancelled. This should
be used by WFE implementations that need to keep track of
the psearches that they are handling.

24 files modified
1209 ■■■■ changed files
opends/src/server/org/opends/server/api/ClientConnection.java 15 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AbandonOperationBasis.java 9 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AddOperationBasis.java 64 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DeleteOperationBasis.java 66 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 55 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyDNOperationBasis.java 67 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyOperationBasis.java 68 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/OperationWrapper.java 8 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PersistentSearch.java 402 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SearchOperationBasis.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java 8 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java 6 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java 27 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AbstractOperation.java 47 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Operation.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java 46 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java 15 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java 45 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java 48 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java 84 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java 25 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java 79 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/ClientConnection.java
@@ -32,6 +32,7 @@
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -45,7 +46,6 @@
import org.opends.server.core.SearchOperation;
import org.opends.server.core.networkgroups.NetworkGroup;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.AbstractOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
@@ -814,8 +814,7 @@
   * @return  The set of operations in progress for this client
   *          connection.
   */
  public abstract Collection<AbstractOperation>
                                      getOperationsInProgress();
  public abstract Collection<Operation> getOperationsInProgress();
@@ -828,8 +827,7 @@
   * @return  The operation in progress with the specified message ID,
   *          or {@code null} if no such operation could be found.
   */
  public abstract AbstractOperation
                          getOperationInProgress(int messageID);
  public abstract Operation getOperationInProgress(int messageID);
@@ -857,8 +855,7 @@
   * @return  The set of persistent searches registered for this
   *          client.
   */
  public final CopyOnWriteArrayList<PersistentSearch>
                    getPersistentSearches()
  public final List<PersistentSearch> getPersistentSearches()
  {
    return persistentSearches;
  }
@@ -1660,13 +1657,13 @@
    if ((authzDN == null) || authzDN.isNullDN())
    {
      return java.util.Collections.<Group>emptySet();
      return Collections.<Group>emptySet();
    }
    Entry userEntry = DirectoryServer.getEntry(authzDN);
    if (userEntry == null)
    {
      return java.util.Collections.<Group>emptySet();
      return Collections.<Group>emptySet();
    }
    HashSet<Group> groupSet = new HashSet<Group>();
opends/src/server/org/opends/server/core/AbandonOperationBasis.java
@@ -39,8 +39,6 @@
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.loggers.debug.DebugLogger;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
import org.opends.server.types.operation.PostOperationAbandonOperation;
import org.opends.server.types.operation.PreParseAbandonOperation;
@@ -57,11 +55,6 @@
               PostOperationAbandonOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = DebugLogger.getTracer();
  // The message ID of the operation that should be abandoned.
  private final int idToAbandon;
@@ -251,7 +244,7 @@
      // code to reflect whether the abandon was successful and an error message
      // if it was not.  Even though there is no response, the result should
      // still be logged.
      AbstractOperation operation =
      Operation operation =
           clientConnection.getOperationInProgress(idToAbandon);
      if (operation == null)
      {
opends/src/server/org/opends/server/core/AddOperationBasis.java
@@ -25,7 +25,6 @@
 *      Copyright 2007-2008 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
@@ -38,10 +37,8 @@
import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
import static org.opends.server.loggers.AccessLogger.logAddRequest;
import static org.opends.server.loggers.AccessLogger.logAddResponse;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.util.ArrayList;
@@ -797,9 +794,10 @@
      // Log the add response message.
      logAddResponse(this);
      // Notifies any persistent searches that might be registered with the
      // server.
      notifyPersistentSearches(workflowExecuted);
      // Invoke the post-response callbacks.
      if (workflowExecuted) {
        invokePostResponseCallbacks();
      }
      // Invoke the post-response add plugins.
      invokePostResponsePlugins(workflowExecuted);
@@ -855,60 +853,6 @@
  }
  /**
   * Notifies any persistent searches that might be registered with the server.
   * If no workflow has been executed then don't notify persistent searches.
   *
   * @param workflowExecuted <code>true</code> if a workflow has been
   *                         executed
   */
  private void notifyPersistentSearches(boolean workflowExecuted)
  {
    if (! workflowExecuted)
    {
      return;
    }
    List<?> localOperations =
      (List<?>)getAttachment(Operation.LOCALBACKENDOPERATIONS);
    if (localOperations != null)
    {
      for (Object localOp : localOperations)
      {
        LocalBackendAddOperation localOperation =
          (LocalBackendAddOperation)localOp;
        if ((getResultCode() == ResultCode.SUCCESS) &&
            (localOperation.getEntryToAdd() != null))
        {
          for (PersistentSearch persistentSearch :
            DirectoryServer.getPersistentSearches())
          {
            try
            {
              persistentSearch.processAdd(localOperation,
                  localOperation.getEntryToAdd());
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message message = ERR_ADD_ERROR_NOTIFYING_PERSISTENT_SEARCH.get(
                  String.valueOf(persistentSearch), getExceptionMessage(e));
              logError(message);
              DirectoryServer.deregisterPersistentSearch(persistentSearch);
            }
          }
        }
      }
    }
  }
  /**
   * Updates the error message and the result code of the operation.
opends/src/server/org/opends/server/core/DeleteOperationBasis.java
@@ -25,7 +25,6 @@
 *      Copyright 2007-2008 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
@@ -37,11 +36,8 @@
import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
import static org.opends.server.loggers.AccessLogger.logDeleteRequest;
import static org.opends.server.loggers.AccessLogger.logDeleteResponse;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -452,9 +448,10 @@
      // Log the delete response.
      logDeleteResponse(this);
      // Notifies any persistent searches that might be registered with the
      // server.
      notifyPersistentSearches(workflowExecuted);
      // Invoke the post-response callbacks.
      if (workflowExecuted) {
        invokePostResponseCallbacks();
      }
      // Invoke the post-response delete plugins.
      invokePostResponsePlugins(workflowExecuted);
@@ -511,61 +508,6 @@
  /**
   * Notifies any persistent searches that might be registered with the server.
   * If no workflow has been executed then don't notify persistent searches.
   *
   * @param workflowExecuted <code>true</code> if a workflow has been
   *                         executed
   */
  private void notifyPersistentSearches(boolean workflowExecuted)
  {
    if (! workflowExecuted)
    {
      return;
    }
    List localOperations =
      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
    if (localOperations != null)
    {
      for (Object localOp : localOperations)
      {
        LocalBackendDeleteOperation localOperation =
          (LocalBackendDeleteOperation)localOp;
        // Notify any persistent searches that might be registered with the
        // server.
        if (getResultCode() == ResultCode.SUCCESS)
        {
          for (PersistentSearch persistentSearch :
            DirectoryServer.getPersistentSearches())
          {
            try
            {
              persistentSearch.processDelete(localOperation,
                  localOperation.getEntryToDelete());
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message message = ERR_DELETE_ERROR_NOTIFYING_PERSISTENT_SEARCH.
                  get(String.valueOf(persistentSearch), getExceptionMessage(e));
              logError(message);
              DirectoryServer.deregisterPersistentSearch(persistentSearch);
            }
          }
        }
      }
    }
  }
  /**
   * Updates the error message and the result code of the operation.
   *
   * This method is called because no workflows were found to process
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -497,9 +497,6 @@
  // The set of import task listeners registered with the Directory Server.
  private CopyOnWriteArrayList<ImportTaskListener> importTaskListeners;
  // The set of persistent searches registered with the Directory Server.
  private CopyOnWriteArrayList<PersistentSearch> persistentSearches;
  // The set of restore task listeners registered with the Directory Server.
  private CopyOnWriteArrayList<RestoreTaskListener> restoreTaskListeners;
@@ -937,8 +934,6 @@
      directoryServer.baseDnRegistry = new BaseDnRegistry();
      directoryServer.changeNotificationListeners =
           new CopyOnWriteArrayList<ChangeNotificationListener>();
      directoryServer.persistentSearches =
           new CopyOnWriteArrayList<PersistentSearch>();
      directoryServer.shutdownListeners =
           new CopyOnWriteArrayList<ServerShutdownListener>();
      directoryServer.synchronizationProviders =
@@ -7730,56 +7725,6 @@
  /**
   * Retrieves the set of persistent searches registered with the Directory
   * Server.
   *
   * @return  The set of persistent searches registered with the Directory
   *          Server.
   */
  public static CopyOnWriteArrayList<PersistentSearch> getPersistentSearches()
  {
    return directoryServer.persistentSearches;
  }
  /**
   * Registers the provided persistent search operation with the Directory
   * Server 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
   *                           the Directory Server.
   */
  public static void registerPersistentSearch(PersistentSearch persistentSearch)
  {
    directoryServer.persistentSearches.add(persistentSearch);
    persistentSearch.getSearchOperation().getClientConnection().
         registerPersistentSearch(persistentSearch);
  }
  /**
   * Deregisters the provided persistent search operation with the Directory
   * Server so that it will no longer be notified of any add, delete, modify, or
   * modify DN operations that are performed.
   *
   * @param  persistentSearch  The persistent search operation to deregister
   *                           with the Directory Server.
   */
  public static void deregisterPersistentSearch(PersistentSearch
                                                     persistentSearch)
  {
    directoryServer.persistentSearches.remove(persistentSearch);
    persistentSearch.getSearchOperation().getClientConnection().
         deregisterPersistentSearch(persistentSearch);
  }
  /**
   * Retrieves the set of synchronization providers that have been registered
   * with the Directory Server.
   *
opends/src/server/org/opends/server/core/ModifyDNOperationBasis.java
@@ -25,7 +25,6 @@
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import java.util.ArrayList;
@@ -47,9 +46,7 @@
import org.opends.server.loggers.debug.DebugLogger;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.loggers.ErrorLogger;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.util.StaticUtils.*;
/**
@@ -665,9 +662,10 @@
      // Log the modify DN response.
      logModifyDNResponse(this);
      // Notifies any persistent searches that might be registered with the
      // server.
      notifyPersistentSearches(workflowExecuted);
      // Invoke the post-response callbacks.
      if (workflowExecuted) {
        invokePostResponseCallbacks();
      }
      // Invoke the post-response modify DN plugins.
      invokePostResponsePlugins(workflowExecuted);
@@ -724,63 +722,6 @@
  /**
   * Notifies any persistent searches that might be registered with the server.
   * If no workflow has been executed then don't notify persistent searches.
   *
   * @param workflowExecuted <code>true</code> if a workflow has been
   *                         executed
   */
  private void notifyPersistentSearches(boolean workflowExecuted)
  {
    if (! workflowExecuted)
    {
      return;
    }
    List localOperations =
      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
    if (localOperations != null)
    {
      for (Object localOperation : localOperations)
      {
        LocalBackendModifyDNOperation localOp =
          (LocalBackendModifyDNOperation)localOperation;
        // Notify any persistent searches that might be registered with
        // the server.
        if (getResultCode() == ResultCode.SUCCESS)
        {
          for (PersistentSearch persistentSearch :
            DirectoryServer.getPersistentSearches())
          {
            try
            {
              persistentSearch.processModifyDN(
                  localOp,
                  localOp.getOriginalEntry(),
                  localOp.getUpdatedEntry());
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message message = ERR_MODDN_ERROR_NOTIFYING_PERSISTENT_SEARCH.get(
                  String.valueOf(persistentSearch), getExceptionMessage(e));
              ErrorLogger.logError(message);
              DirectoryServer.deregisterPersistentSearch(persistentSearch);
            }
          }
        }
      }
    }
  }
  /**
   * Updates the error message and the result code of the operation.
   *
   * This method is called because no workflows were found to process
opends/src/server/org/opends/server/core/ModifyOperationBasis.java
@@ -27,9 +27,6 @@
package org.opends.server.core;
import org.opends.messages.MessageBuilder;
import org.opends.messages.Message;
import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ENTRY_DN;
import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ERROR_MESSAGE;
import static org.opends.server.core.CoreConstants.LOG_ELEMENT_MATCHED_DN;
@@ -38,11 +35,8 @@
import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
import static org.opends.server.loggers.AccessLogger.logModifyRequest;
import static org.opends.server.loggers.AccessLogger.logModifyResponse;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -554,9 +548,10 @@
      // Log the modify response.
      logModifyResponse(this);
      // Notifies any persistent searches that might be registered with the
      // server.
      notifyPersistentSearches(workflowExecuted);
      // Invoke the post-response callbacks.
      if (workflowExecuted) {
        invokePostResponseCallbacks();
      }
      // Invoke the post-response add plugins.
      invokePostResponsePlugins(workflowExecuted);
@@ -612,61 +607,6 @@
  }
  /**
   * Notifies any persistent searches that might be registered with the server.
   * If no workflow has been executed then don't notify persistent searches.
   *
   * @param workflowExecuted <code>true</code> if a workflow has been
   *                         executed
   */
  private void notifyPersistentSearches(boolean workflowExecuted)
  {
    if (! workflowExecuted)
    {
      return;
    }
    List localOperations =
      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
    if (localOperations != null)
    {
      for (Object localOp : localOperations)
      {
        LocalBackendModifyOperation localOperation =
          (LocalBackendModifyOperation)localOp;
        // Notify any persistent searches that might be registered with
        // the server.
        if (localOperation.getResultCode() == ResultCode.SUCCESS)
        {
          for (PersistentSearch persistentSearch :
               DirectoryServer.getPersistentSearches())
          {
            try
            {
              persistentSearch.processModify(localOperation,
                  localOperation.getCurrentEntry(),
                  localOperation.getModifiedEntry());
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message message = ERR_MODIFY_ERROR_NOTIFYING_PERSISTENT_SEARCH.
                  get(String.valueOf(persistentSearch), getExceptionMessage(e));
              logError(message);
              DirectoryServer.deregisterPersistentSearch(persistentSearch);
            }
          }
        }
      }
    }
  }
  /**
   * Updates the error message and the result code of the operation.
opends/src/server/org/opends/server/core/OperationWrapper.java
@@ -484,5 +484,13 @@
      throws CanceledOperationException {
    operation.checkIfCanceled(signalTooLate);
  }
  /**
   * {@inheritDoc}
   */
  public void registerPostResponseCallback(Runnable callback)
  {
    operation.registerPostResponseCallback(callback);
  }
}
opends/src/server/org/opends/server/core/PersistentSearch.java
@@ -27,161 +27,177 @@
package org.opends.server.core;
import static org.opends.server.loggers.debug.DebugLogger.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.opends.server.controls.EntryChangeNotificationControl;
import org.opends.server.controls.PersistentSearchChangeType;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.CancelResult;
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.workflowelement.localbackend.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
/**
 * This class defines a data structure that will be used to hold the information
 * necessary for processing a persistent search.
 * This class defines a data structure that will be used to hold the
 * information necessary for processing a persistent search.
 */
public class PersistentSearch
public final class PersistentSearch
{
  /**
   * A cancellation call-back which can be used by work-flow element
   * implementations in order to register for resource cleanup when a
   * persistent search is cancelled.
   */
  public static interface CancellationCallback
  {
    /**
     * The provided persistent search has been cancelled. Any
     * resources associated with the persistent search should be
     * released.
     *
     * @param psearch
     *          The persistent search which has just been cancelled.
     */
    void persistentSearchCancelled(PersistentSearch psearch);
  }
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // Indicates whether entries returned should include the entry change
  // notification control.
  private boolean returnECs;
  // Indicates whether entries returned should include the entry
  // change notification control.
  private final boolean returnECs;
  // The base DN for the search operation.
  private DN baseDN;
  private final DN baseDN;
  // The set of change types we want to see.
  private Set<PersistentSearchChangeType> changeTypes;
  private final Set<PersistentSearchChangeType> changeTypes;
  // The scope for the search operation.
  private SearchScope scope;
  private final SearchScope scope;
  // The filter for the search operation.
  private SearchFilter filter;
  private final SearchFilter filter;
  // The reference to the associated search operation.
  private SearchOperation searchOperation;
  private final SearchOperation searchOperation;
  // Indicates whether or not this persistent search has already been
  // aborted.
  private boolean isCancelled = false;
  // Cancellation callbacks which should be run when this persistent
  // search is cancelled.
  private final List<CancellationCallback> cancellationCallbacks =
    new CopyOnWriteArrayList<CancellationCallback>();
  /**
   * 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  returnECs        Indicates whether to include entry change
   *                          notification controls in search result entries
   *                          sent to the client.
   * @param searchOperation
   *          The search operation for this persistent search.
   * @param changeTypes
   *          The change types for which changes should be examined.
   * @param returnECs
   *          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 returnECs)
  {
    this.searchOperation = searchOperation;
    this.changeTypes     = changeTypes;
    this.returnECs       = returnECs;
    baseDN = searchOperation.getBaseDN();
    scope  = searchOperation.getScope();
    filter = searchOperation.getFilter();
    this.baseDN = searchOperation.getBaseDN();
    this.scope = searchOperation.getScope();
    this.filter = searchOperation.getFilter();
  }
  /**
   * Retrieves the search operation for this persistent search.
   * Cancels this persistent search operation. On exit this persistent
   * search will no longer be valid and any resources associated with
   * it will have been released.
   *
   * @return  The search operation for this persistent search.
   * @return The result of the cancellation.
   */
  public SearchOperation getSearchOperation()
  public synchronized CancelResult cancel()
  {
    return searchOperation;
  }
  /**
   * Retrieves the set of change types for this persistent search.
   *
   * @return  The set of change types for this persistent search.
   */
  public Set<PersistentSearchChangeType> getChangeTypes()
    if (!isCancelled)
  {
    return changeTypes;
  }
      isCancelled = true;
      // The persistent search can no longer be cancelled.
      searchOperation.getClientConnection().deregisterPersistentSearch(this);
  /**
   * Retrieves the returnECs flag for this persistent search.
   *
   * @return  The return ECs flag for this persistent search.
   */
  public boolean getReturnECs()
      // Notify any cancellation callbacks.
      for (CancellationCallback callback : cancellationCallbacks)
  {
    return returnECs;
  }
  /**
   * Retrieves the base DN for this persistent search.
   *
   * @return  The base DN for this persistent search.
   */
  public DN getBaseDN()
        try
  {
    return baseDN;
          callback.persistentSearchCancelled(this);
  }
  /**
   * Retrieves the scope for this persistent search.
   *
   * @return  The scope for this persistent search.
   */
  public SearchScope getScope()
        catch (Exception e)
  {
    return scope;
  }
  /**
   * Retrieves the filter for this persistent search.
   *
   * @return  The filter for this persistent search.
   */
  public SearchFilter getFilter()
          if (debugEnabled())
  {
    return filter;
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      }
    }
    return new CancelResult(ResultCode.CANCELED, null);
  }
  /**
   * Performs any necessary processing for the provided add operation.
   * Gets the message ID associated with this persistent search.
   *
   * @param  addOperation  The add operation that has been processed.
   * @param  entry         The entry that was added.
   * @return The message ID associated with this persistent search.
   */
  public void processAdd(LocalBackendAddOperation addOperation, Entry entry)
  public int getMessageID()
  {
    return searchOperation.getMessageID();
  }
  /**
   * Notifies the persistent searches that an entry has been added.
   *
   * @param entry
   *          The entry that was added.
   * @param changeNumber
   *          The change number associated with the operation that
   *          added the entry, or {@code -1} if there is no change
   *          number.
   */
  public void processAdd(Entry entry, long changeNumber)
  {
    // See if we care about add operations.
    if (! changeTypes.contains(PersistentSearchChangeType.ADD))
@@ -189,7 +205,6 @@
      return;
    }
    // Make sure that the entry is within our target scope.
    switch (scope)
    {
@@ -212,8 +227,7 @@
        }
        break;
      case SUBORDINATE_SUBTREE:
        if (baseDN.equals(entry.getDN()) ||
            (! baseDN.isAncestorOf(entry.getDN())))
      if (baseDN.equals(entry.getDN()) || (!baseDN.isAncestorOf(entry.getDN())))
        {
          return;
        }
@@ -222,7 +236,6 @@
        return;
    }
    // Make sure that the entry matches the target filter.
    try
    {
@@ -243,25 +256,22 @@
      return;
    }
    // The entry is one that should be sent to the client.  See if we also need
    // to construct an entry change notification control.
    // The entry is one that should be sent to the client. See if we
    // also need to construct an entry change notification control.
    ArrayList<Control> entryControls = new ArrayList<Control>(1);
    if (returnECs)
    {
      entryControls.add(new EntryChangeNotificationControl(
                                 PersistentSearchChangeType.ADD,
                                 addOperation.getChangeNumber()));
          PersistentSearchChangeType.ADD, changeNumber));
    }
    // Send the entry and see if we should continue processing.  If not, then
    // deregister this persistent search.
    // Send the entry and see if we should continue processing. If
    // not, then deregister this persistent search.
    try
    {
      if (! searchOperation.returnEntry(entry, entryControls))
      {
        DirectoryServer.deregisterPersistentSearch(this);
        cancel();
        searchOperation.sendSearchResultDone();
      }
    }
@@ -272,7 +282,7 @@
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      DirectoryServer.deregisterPersistentSearch(this);
      cancel();
      try
      {
@@ -291,13 +301,16 @@
  /**
   * Performs any necessary processing for the provided delete operation.
   * Notifies the persistent searches that an entry has been deleted.
   *
   * @param  deleteOperation  The delete operation that has been processed.
   * @param  entry            The entry that was removed.
   * @param entry
   *          The entry that was deleted.
   * @param changeNumber
   *          The change number associated with the operation that
   *          deleted the entry, or {@code -1} if there is no change
   *          number.
   */
  public void processDelete(LocalBackendDeleteOperation deleteOperation,
      Entry entry)
  public void processDelete(Entry entry, long changeNumber)
  {
    // See if we care about delete operations.
    if (! changeTypes.contains(PersistentSearchChangeType.DELETE))
@@ -305,7 +318,6 @@
      return;
    }
    // Make sure that the entry is within our target scope.
    switch (scope)
    {
@@ -328,8 +340,7 @@
        }
        break;
      case SUBORDINATE_SUBTREE:
        if (baseDN.equals(entry.getDN()) ||
            (! baseDN.isAncestorOf(entry.getDN())))
      if (baseDN.equals(entry.getDN()) || (!baseDN.isAncestorOf(entry.getDN())))
        {
          return;
        }
@@ -338,7 +349,6 @@
        return;
    }
    // Make sure that the entry matches the target filter.
    try
    {
@@ -359,25 +369,22 @@
      return;
    }
    // The entry is one that should be sent to the client.  See if we also need
    // to construct an entry change notification control.
    // The entry is one that should be sent to the client. See if we
    // also need to construct an entry change notification control.
    ArrayList<Control> entryControls = new ArrayList<Control>(1);
    if (returnECs)
    {
      entryControls.add(new EntryChangeNotificationControl(
                                 PersistentSearchChangeType.DELETE,
                                 deleteOperation.getChangeNumber()));
          PersistentSearchChangeType.DELETE, changeNumber));
    }
    // Send the entry and see if we should continue processing.  If not, then
    // deregister this persistent search.
    // Send the entry and see if we should continue processing. If
    // not, then deregister this persistent search.
    try
    {
      if (! searchOperation.returnEntry(entry, entryControls))
      {
        DirectoryServer.deregisterPersistentSearch(this);
        cancel();
        searchOperation.sendSearchResultDone();
      }
    }
@@ -388,7 +395,7 @@
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      DirectoryServer.deregisterPersistentSearch(this);
      cancel();
      try
      {
@@ -407,15 +414,35 @@
  /**
   * Performs any necessary processing for the provided modify operation.
   * Notifies the persistent searches that an entry has been modified.
   *
   * @param  modifyOperation  The modify operation that has been processed.
   * @param  oldEntry         The entry before the modification was applied.
   * @param  newEntry         The entry after the modification was applied.
   * @param entry
   *          The entry after it was modified.
   * @param changeNumber
   *          The change number associated with the operation that
   *          modified the entry, or {@code -1} if there is no change
   *          number.
   */
  public void processModify(LocalBackendModifyOperation modifyOperation,
                            Entry oldEntry,
                            Entry newEntry)
  public void processModify(Entry entry, long changeNumber)
  {
    processModify(entry, changeNumber, entry);
  }
  /**
   * Notifies persistent searches that an entry has been modified.
   *
   * @param entry
   *          The entry after it was modified.
   * @param changeNumber
   *          The change number associated with the operation that
   *          modified the entry, or {@code -1} if there is no change
   *          number.
   * @param oldEntry
   *          The entry before it was modified.
   */
  public void processModify(Entry entry, long changeNumber, Entry oldEntry)
  {
    // See if we care about modify operations.
    if (! changeTypes.contains(PersistentSearchChangeType.MODIFY))
@@ -423,7 +450,6 @@
      return;
    }
    // Make sure that the entry is within our target scope.
    switch (scope)
    {
@@ -434,7 +460,7 @@
        }
        break;
      case SINGLE_LEVEL:
        if (! baseDN.equals(oldEntry.getDN().getParentDNInSuffix()))
      if (!baseDN.equals(oldEntry.getDN().getParent()))
        {
          return;
        }
@@ -446,8 +472,8 @@
        }
        break;
      case SUBORDINATE_SUBTREE:
        if (baseDN.equals(oldEntry.getDN()) ||
            (! baseDN.isAncestorOf(oldEntry.getDN())))
      if (baseDN.equals(oldEntry.getDN())
          || (!baseDN.isAncestorOf(oldEntry.getDN())))
        {
          return;
        }
@@ -456,12 +482,10 @@
        return;
    }
    // Make sure that the entry matches the target filter.
    try
    {
      if ((! filter.matchesEntry(oldEntry)) &&
          (! filter.matchesEntry(newEntry)))
      if ((!filter.matchesEntry(oldEntry)) && (!filter.matchesEntry(entry)))
      {
        return;
      }
@@ -478,25 +502,22 @@
      return;
    }
    // The entry is one that should be sent to the client.  See if we also need
    // to construct an entry change notification control.
    // The entry is one that should be sent to the client. See if we
    // also need to construct an entry change notification control.
    ArrayList<Control> entryControls = new ArrayList<Control>(1);
    if (returnECs)
    {
      entryControls.add(new EntryChangeNotificationControl(
                                 PersistentSearchChangeType.MODIFY,
                                 modifyOperation.getChangeNumber()));
          PersistentSearchChangeType.MODIFY, changeNumber));
    }
    // Send the entry and see if we should continue processing.  If not, then
    // deregister this persistent search.
    // Send the entry and see if we should continue processing. If
    // not, then deregister this persistent search.
    try
    {
      if (! searchOperation.returnEntry(newEntry, entryControls))
      if (!searchOperation.returnEntry(entry, entryControls))
      {
        DirectoryServer.deregisterPersistentSearch(this);
        cancel();
        searchOperation.sendSearchResultDone();
      }
    }
@@ -507,7 +528,7 @@
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      DirectoryServer.deregisterPersistentSearch(this);
      cancel();
      try
      {
@@ -526,14 +547,18 @@
  /**
   * Performs any necessary processing for the provided modify DN operation.
   * Notifies the persistent searches that an entry has been renamed.
   *
   * @param  modifyDNOperation  The modify DN operation that has been processed.
   * @param  oldEntry           The entry before the modify DN.
   * @param  newEntry           The entry after the modify DN.
   * @param entry
   *          The entry after it was modified.
   * @param changeNumber
   *          The change number associated with the operation that
   *          modified the entry, or {@code -1} if there is no change
   *          number.
   * @param oldDN
   *          The DN of the entry before it was renamed.
   */
  public void processModifyDN(LocalBackendModifyDNOperation modifyDNOperation,
                              Entry oldEntry, Entry newEntry)
  public void processModifyDN(Entry entry, long changeNumber, DN oldDN)
  {
    // See if we care about modify DN operations.
    if (! changeTypes.contains(PersistentSearchChangeType.MODIFY_DN))
@@ -541,18 +566,18 @@
      return;
    }
    // Make sure that the old or new entry is within our target scope.  In this
    // case, we need to check the DNs of both the old and new entry so we know
    // which one(s) should be compared against the filter.
    // Make sure that the old or new entry is within our target scope.
    // In this case, we need to check the DNs of both the old and new
    // entry so we know which one(s) should be compared against the
    // filter.
    boolean oldMatches = false;
    boolean newMatches = false;
    switch (scope)
    {
      case BASE_OBJECT:
        oldMatches = baseDN.equals(oldEntry.getDN());
        newMatches = baseDN.equals(newEntry.getDN());
      oldMatches = baseDN.equals(oldDN);
      newMatches = baseDN.equals(entry.getDN());
        if (! (oldMatches || newMatches))
        {
@@ -561,8 +586,8 @@
        break;
      case SINGLE_LEVEL:
        oldMatches = baseDN.equals(oldEntry.getDN().getParentDNInSuffix());
        newMatches = baseDN.equals(newEntry.getDN().getParentDNInSuffix());
      oldMatches = baseDN.equals(oldDN.getParent());
      newMatches = baseDN.equals(entry.getDN().getParent());
        if (! (oldMatches || newMatches))
        {
@@ -571,8 +596,8 @@
        break;
      case WHOLE_SUBTREE:
        oldMatches = baseDN.isAncestorOf(oldEntry.getDN());
        newMatches = baseDN.isAncestorOf(newEntry.getDN());
      oldMatches = baseDN.isAncestorOf(oldDN);
      newMatches = baseDN.isAncestorOf(entry.getDN());
        if (! (oldMatches || newMatches))
        {
@@ -581,10 +606,9 @@
        break;
      case SUBORDINATE_SUBTREE:
        oldMatches = ((! baseDN.equals(oldEntry.getDN())) &&
                      baseDN.isAncestorOf(oldEntry.getDN()));
        newMatches = ((! baseDN.equals(newEntry.getDN())) &&
                      baseDN.isAncestorOf(newEntry.getDN()));
      oldMatches = ((!baseDN.equals(oldDN)) && baseDN.isAncestorOf(oldDN));
      newMatches = ((!baseDN.equals(entry.getDN())) && baseDN
          .isAncestorOf(entry.getDN()));
        if (! (oldMatches || newMatches))
        {
@@ -596,12 +620,10 @@
        return;
    }
    // Make sure that the entry matches the target filter.
    try
    {
      if (((! oldMatches) || (! filter.matchesEntry(oldEntry))) &&
          ((! newMatches) && (! filter.matchesEntry(newEntry))))
      if (!oldMatches && !newMatches && !filter.matchesEntry(entry))
      {
        return;
      }
@@ -618,26 +640,22 @@
      return;
    }
    // The entry is one that should be sent to the client.  See if we also need
    // to construct an entry change notification control.
    // The entry is one that should be sent to the client. See if we
    // also need to construct an entry change notification control.
    ArrayList<Control> entryControls = new ArrayList<Control>(1);
    if (returnECs)
    {
      entryControls.add(new EntryChangeNotificationControl(
                                 PersistentSearchChangeType.MODIFY_DN,
                                 oldEntry.getDN(),
                                 modifyDNOperation.getChangeNumber()));
          PersistentSearchChangeType.MODIFY_DN, oldDN, changeNumber));
    }
    // Send the entry and see if we should continue processing.  If not, then
    // deregister this persistent search.
    // Send the entry and see if we should continue processing. If
    // not, then deregister this persistent search.
    try
    {
      if (! searchOperation.returnEntry(newEntry, entryControls))
      if (!searchOperation.returnEntry(entry, entryControls))
      {
        DirectoryServer.deregisterPersistentSearch(this);
        cancel();
        searchOperation.sendSearchResultDone();
      }
    }
@@ -648,7 +666,7 @@
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      DirectoryServer.deregisterPersistentSearch(this);
      cancel();
      try
      {
@@ -667,10 +685,26 @@
  /**
   * Registers a cancellation callback with this persistent search.
   * The cancellation callback will be notified when this persistent
   * search has been cancelled.
   *
   * @param callback
   *          The cancellation callback.
   */
  public void registerCancellationCallback(CancellationCallback callback)
  {
    cancellationCallbacks.add(callback);
  }
  /**
   * Retrieves a string representation of this persistent search.
   *
   * @return  A string representation of this persistent search.
   */
  @Override
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
@@ -681,10 +715,11 @@
  /**
   * Appends a string representation of this persistent search to the provided
   * buffer.
   * Appends a string representation of this persistent search to the
   * provided buffer.
   *
   * @param  buffer  The buffer to which the information should be appended.
   * @param buffer
   *          The buffer to which the information should be appended.
   */
  public void toString(StringBuilder buffer)
  {
@@ -701,4 +736,3 @@
    buffer.append("\")");
  }
}
opends/src/server/org/opends/server/core/SearchOperationBasis.java
@@ -1277,7 +1277,7 @@
      if (persistentSearch != null)
      {
        DirectoryServer.deregisterPersistentSearch(persistentSearch);
        persistentSearch.cancel();
        persistentSearch = null;
      }
    }
opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
@@ -141,7 +141,7 @@
  private AuthenticationInfo authenticationInfo;
  // The empty operation list for this connection.
  private LinkedList<AbstractOperation> operationList;
  private LinkedList<Operation> operationList;
  // The connection ID for this client connection.
  private long connectionID;
@@ -258,7 +258,7 @@
    }
    connectionID  = nextConnectionID.getAndDecrement();
    operationList = new LinkedList<AbstractOperation>();
    operationList = new LinkedList<Operation>();
    try
    {
@@ -298,7 +298,7 @@
    super.setLookthroughLimit(0);
    connectionID  = nextConnectionID.getAndDecrement();
    operationList = new LinkedList<AbstractOperation>();
    operationList = new LinkedList<Operation>();
    try
    {
@@ -2919,7 +2919,7 @@
       mayExtend=false,
       mayInvoke=false)
  @Override()
  public Collection<AbstractOperation> getOperationsInProgress()
  public Collection<Operation> getOperationsInProgress()
  {
    return operationList;
  }
opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
@@ -100,7 +100,7 @@
  private ConnectionSecurityProvider securityProvider;
  // The empty operation list for this connection.
  private LinkedList<AbstractOperation> operationList;
  private LinkedList<Operation> operationList;
  // The connection ID for this client connection.
  private long connectionID;
@@ -155,7 +155,7 @@
          true,
          ERR_LDAP_CONNHANDLER_REJECTED_BY_SERVER.get());
    }
    operationList = new LinkedList<AbstractOperation>();
    operationList = new LinkedList<Operation>();
    try
    {
@@ -1130,7 +1130,7 @@
   *
   * @return  The set of operations in progress for this client connection.
   */
  public Collection<AbstractOperation> getOperationsInProgress()
  public Collection<Operation> getOperationsInProgress()
  {
    return operationList;
  }
opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
@@ -138,7 +138,7 @@
  private byte[] elementValue;
  // The set of all operations currently in progress on this connection.
  private ConcurrentHashMap<Integer,AbstractOperation> operationsInProgress;
  private ConcurrentHashMap<Integer,Operation> operationsInProgress;
  // The number of operations performed on this connection.
  // Used to compare with the resource limits of the network group.
@@ -264,7 +264,7 @@
    nextOperationID      = new AtomicLong(0);
    connectionValid      = true;
    disconnectRequested  = false;
    operationsInProgress = new ConcurrentHashMap<Integer,AbstractOperation>();
    operationsInProgress = new ConcurrentHashMap<Integer,Operation>();
    operationsPerformed = 0;
    operationsPerformedLock = new Object();
    keepStats            = connectionHandler.keepStats();
@@ -1136,7 +1136,7 @@
   *
   * @return  The set of operations in progress for this client connection.
   */
  public Collection<AbstractOperation> getOperationsInProgress()
  public Collection<Operation> getOperationsInProgress()
  {
    return operationsInProgress.values();
  }
@@ -1151,7 +1151,7 @@
   * @return  The operation in progress with the specified message ID, or
   *          <CODE>null</CODE> if no such operation could be found.
   */
  public AbstractOperation getOperationInProgress(int messageID)
  public Operation getOperationInProgress(int messageID)
  {
    return operationsInProgress.get(messageID);
  }
@@ -1192,7 +1192,7 @@
        // See if there is already an operation in progress with the same
        // message ID.  If so, then we can't allow it.
        AbstractOperation op = operationsInProgress.get(messageID);
        Operation op = operationsInProgress.get(messageID);
        if (op != null)
        {
          Message message =
@@ -1252,7 +1252,7 @@
   */
  public boolean removeOperationInProgress(int messageID)
  {
    AbstractOperation operation = operationsInProgress.remove(messageID);
    Operation operation = operationsInProgress.remove(messageID);
    if (operation == null)
    {
      return false;
@@ -1279,16 +1279,15 @@
  public CancelResult cancelOperation(int messageID,
                                      CancelRequest cancelRequest)
  {
    AbstractOperation op = operationsInProgress.get(messageID);
    Operation op = operationsInProgress.get(messageID);
    if (op == null)
    {
      // See if the operation is in the list of persistent searches.
      for (PersistentSearch ps : getPersistentSearches())
      {
        if (ps.getSearchOperation().getMessageID() == messageID)
        if (ps.getMessageID() == messageID)
        {
          CancelResult cancelResult =
               ps.getSearchOperation().cancel(cancelRequest);
          CancelResult cancelResult = ps.cancel();
          if (keepStats && (cancelResult.getResultCode() ==
              ResultCode.CANCELED))
@@ -1329,7 +1328,7 @@
    {
      try
      {
        for (AbstractOperation o : operationsInProgress.values())
        for (Operation o : operationsInProgress.values())
        {
          try
          {
@@ -1361,7 +1360,7 @@
        for (PersistentSearch persistentSearch : getPersistentSearches())
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          persistentSearch.cancel();
        }
      }
      catch (Exception e)
@@ -1400,7 +1399,7 @@
            continue;
          }
          AbstractOperation o = operationsInProgress.get(msgID);
          Operation o = operationsInProgress.get(msgID);
          if (o != null)
          {
            try
@@ -1429,7 +1428,7 @@
        for (PersistentSearch persistentSearch : getPersistentSearches())
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          persistentSearch.cancel();
          lastCompletionTime.set(TimeThread.getTime());
        }
      }
opends/src/server/org/opends/server/types/AbstractOperation.java
@@ -33,12 +33,14 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.opends.server.api.ClientConnection;
import org.opends.server.types.operation.PostResponseOperation;
import org.opends.server.types.operation.PreParseOperation;
import org.opends.server.core.DirectoryServer;
import static org.opends.server.loggers.debug.
    DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
@@ -168,6 +170,9 @@
  // nanoseconds.
  private long processingStopNanoTime;
  // The callbacks to be invoked once a response has been sent.
  private List<Runnable> postResponseCallbacks = null;
  /**
   * Creates a new operation with the provided information.
   *
@@ -1148,5 +1153,47 @@
   * processing.
   */
  public abstract void run();
  /**
   * {@inheritDoc}
   */
  public final void registerPostResponseCallback(Runnable callback)
  {
    if (postResponseCallbacks == null)
    {
      postResponseCallbacks = new LinkedList<Runnable>();
    }
    postResponseCallbacks.add(callback);
  }
  /**
   * Invokes the post response callbacks that were registered with
   * this operation.
   */
  protected final void invokePostResponseCallbacks()
  {
    if (postResponseCallbacks != null)
    {
      for (Runnable callback : postResponseCallbacks)
      {
        try
        {
          callback.run();
        }
        catch (Exception e)
        {
          // Should not happen.
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      }
    }
  }
}
opends/src/server/org/opends/server/types/Operation.java
@@ -622,5 +622,15 @@
  public void checkIfCanceled(boolean signalTooLate)
      throws CanceledOperationException;
  /**
   * Registers a callback which should be run once this operation has
   * completed and the response sent back to the client.
   *
   * @param callback
   *          The callback to be run once this operation has completed
   *          and the response sent back to the client.
   */
  public void registerPostResponseCallback(Runnable callback);
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -65,6 +65,7 @@
import org.opends.server.core.AddOperationWrapper;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PasswordPolicy;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.schema.AuthPasswordSyntax;
@@ -173,16 +174,17 @@
  /**
   * Process this add operation against a local backend.
   *
   * @param  backend  The backend in which the add operation should be
   *                  processed.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   * @param wfe
   *          The local backend work-flow element.
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalAdd(Backend backend) throws CanceledOperationException {
  void processLocalAdd(final LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
    this.backend = backend;
    this.backend = wfe.getBackend();
    ClientConnection clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
@@ -772,17 +774,27 @@
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    if ((getResultCode() == ResultCode.SUCCESS) && (entry != null))
    // Register a post-response call-back which will notify persistent
    // searches and change listeners.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      registerPostResponseCallback(new Runnable()
      {
        public void run()
        {
          // Notify persistent searches.
          for (PersistentSearch psearch : wfe.getPersistentSearches()) {
            psearch.processAdd(entry, getChangeNumber());
          }
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleAddOperation(this, entry);
              changeListener.handleAddOperation(LocalBackendAddOperation.this,
                  entry);
        }
        catch (Exception e)
        {
@@ -791,11 +803,13 @@
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          logError(ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER.get(
                        getExceptionMessage(e)));
              logError(ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER
                  .get(getExceptionMessage(e)));
        }
      }
    }
      });
    }
  }
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
@@ -178,12 +178,13 @@
  /**
   * Process this bind operation in a local backend.
   *
   * @param  backend  The backend in which the bind operation should be
   *                  processed.
   * @param wfe
   *          The local backend work-flow element.
   *
   */
  void processLocalBind(Backend backend)
  void processLocalBind(LocalBackendWorkflowElement wfe)
  {
    this.backend = backend;
    this.backend = wfe.getBackend();
    // Initialize a number of variables for use during the bind processing.
    clientConnection         = getClientConnection();
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java
@@ -116,16 +116,17 @@
  /**
   * Process this compare operation in a local backend.
   *
   * @param  backend  The backend in which the compare operation should be
   *                  processed.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   * @param wfe
   *          The local backend work-flow element.
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalCompare(Backend backend) throws CanceledOperationException {
  void processLocalCompare(LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
    this.backend = backend;
    this.backend = wfe.getBackend();
    clientConnection  = getClientConnection();
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -47,6 +47,7 @@
import org.opends.server.core.DeleteOperationWrapper;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.AttributeType;
@@ -143,16 +144,16 @@
  /**
   * Process this delete operation in a local backend.
   *
   * @param  backend  The backend in which the delete operation should be
   *                  processed.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   * @param wfe
   *          The local backend work-flow element.
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalDelete(Backend backend) throws CanceledOperationException {
  void processLocalDelete(final LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
    this.backend = backend;
    this.backend = wfe.getBackend();
    clientConnection = getClientConnection();
@@ -444,16 +445,28 @@
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    // Register a post-response call-back which will notify persistent
    // searches and change listeners.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      registerPostResponseCallback(new Runnable()
      {
        public void run()
        {
          // Notify persistent searches.
          for (PersistentSearch psearch : wfe.getPersistentSearches()) {
            psearch.processDelete(entry, getChangeNumber());
          }
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleDeleteOperation(this, entry);
              changeListener.handleDeleteOperation(
                  LocalBackendDeleteOperation.this, entry);
        }
        catch (Exception e)
        {
@@ -462,12 +475,14 @@
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          Message message = ERR_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
              Message message = ERR_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER
                  .get(getExceptionMessage(e));
          logError(message);
        }
      }
    }
      });
    }
  }
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -51,6 +51,7 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyDNOperationWrapper;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.Attribute;
@@ -180,16 +181,16 @@
  /**
   * Process this modify DN operation in a local backend.
   *
   * @param  backend  The backend in which the modify DN operation should be
   *                  processed.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   * @param wfe
   *          The local backend work-flow element.
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalModifyDN(Backend backend) throws CanceledOperationException {
  void processLocalModifyDN(final LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
    this.backend = backend;
    this.backend = wfe.getBackend();
    clientConnection = getClientConnection();
@@ -632,17 +633,30 @@
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    // Register a post-response call-back which will notify persistent
    // searches and change listeners.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      registerPostResponseCallback(new Runnable()
      {
        public void run()
        {
          // Notify persistent searches.
          for (PersistentSearch psearch : wfe.getPersistentSearches())
          {
            psearch.processModifyDN(newEntry, getChangeNumber(),
                currentEntry.getDN());
          }
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleModifyDNOperation(this, currentEntry, newEntry);
              changeListener.handleModifyDNOperation(
                  LocalBackendModifyDNOperation.this, currentEntry, newEntry);
        }
        catch (Exception e)
        {
@@ -651,12 +665,14 @@
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
              Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER
                  .get(getExceptionMessage(e));
          logError(message);
        }
      }
    }
      });
    }
  }
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -65,6 +65,7 @@
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyOperationWrapper;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1OctetString;
@@ -293,16 +294,16 @@
  /**
   * Process this modify operation against a local backend.
   *
   * @param  backend  The backend in which the modify operation should be
   *                  performed.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   * @param wfe
   *          The local backend work-flow element.
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalModify(Backend backend) throws CanceledOperationException {
  void processLocalModify(final LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
    this.backend = backend;
    this.backend = wfe.getBackend();
    clientConnection = getClientConnection();
@@ -690,11 +691,46 @@
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    // Register a post-response call-back which will notify persistent
    // searches and change listeners.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      notifyChangeListeners();
      registerPostResponseCallback(new Runnable()
      {
        public void run()
        {
          // Notify persistent searches.
          for (PersistentSearch psearch : wfe.getPersistentSearches())
          {
            psearch.processModify(modifiedEntry, getChangeNumber(),
                currentEntry);
          }
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
          {
            try
            {
              changeListener
                  .handleModifyOperation(LocalBackendModifyOperation.this,
                      currentEntry, modifiedEntry);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message message = ERR_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER
                  .get(getExceptionMessage(e));
              logError(message);
            }
          }
        }
      });
    }
  }
@@ -2228,32 +2264,6 @@
  /**
   * Notify any registered change listeners about this update.
   */
  private void notifyChangeListeners()
  {
    for (ChangeNotificationListener changeListener :
         DirectoryServer.getChangeNotificationListeners())
    {
      try
      {
        changeListener.handleModifyOperation(this, currentEntry, modifiedEntry);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        Message message = ERR_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER.get(
            getExceptionMessage(e));
        logError(message);
      }
    }
  }
  private boolean handleConflictResolution() {
      boolean returnVal = true;
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
@@ -121,16 +121,16 @@
  /**
   * Process this search operation against a local backend.
   *
   * @param  backend  The backend in which the search operation should be
   *                  performed.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   * @param wfe
   *          The local backend work-flow element.
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalSearch(Backend backend) throws CanceledOperationException {
  void processLocalSearch(LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
    this.backend = backend;
    this.backend = wfe.getBackend();
    clientConnection = getClientConnection();
@@ -230,7 +230,8 @@
      // If there's a persistent search, then register it with the server.
      if (persistentSearch != null)
      {
        DirectoryServer.registerPersistentSearch(persistentSearch);
        wfe.registerPersistentSearch(persistentSearch);
        clientConnection.registerPersistentSearch(persistentSearch);
        setSendResponse(false);
      }
@@ -254,7 +255,7 @@
        if (persistentSearch != null)
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          persistentSearch.cancel();
          setSendResponse(true);
        }
@@ -264,7 +265,7 @@
      {
        if (persistentSearch != null)
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          persistentSearch.cancel();
          setSendResponse(true);
        }
@@ -283,7 +284,7 @@
        if (persistentSearch != null)
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          persistentSearch.cancel();
          setSendResponse(true);
        }
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
@@ -47,6 +48,7 @@
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.SearchOperation;
import org.opends.server.types.*;
import org.opends.server.workflowelement.LeafWorkflowElement;
@@ -73,6 +75,10 @@
       registeredLocalBackends =
            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
  // variable
@@ -154,6 +160,12 @@
    // an NPE
    super.initialize(null, null);
    backend = null;
    // Cancel all persistent searches.
    for (PersistentSearch psearch : persistentSearches) {
      psearch.cancel();
    }
    persistentSearches.clear();
  }
@@ -372,43 +384,43 @@
      case BIND:
        LocalBackendBindOperation bindOperation =
             new LocalBackendBindOperation((BindOperation) operation);
        bindOperation.processLocalBind(backend);
        bindOperation.processLocalBind(this);
        break;
      case SEARCH:
        LocalBackendSearchOperation searchOperation =
             new LocalBackendSearchOperation((SearchOperation) operation);
        searchOperation.processLocalSearch(backend);
        searchOperation.processLocalSearch(this);
        break;
      case ADD:
        LocalBackendAddOperation addOperation =
             new LocalBackendAddOperation((AddOperation) operation);
        addOperation.processLocalAdd(backend);
        addOperation.processLocalAdd(this);
        break;
      case DELETE:
        LocalBackendDeleteOperation deleteOperation =
             new LocalBackendDeleteOperation((DeleteOperation) operation);
        deleteOperation.processLocalDelete(backend);
        deleteOperation.processLocalDelete(this);
        break;
      case MODIFY:
        LocalBackendModifyOperation modifyOperation =
             new LocalBackendModifyOperation((ModifyOperation) operation);
        modifyOperation.processLocalModify(backend);
        modifyOperation.processLocalModify(this);
        break;
      case MODIFY_DN:
        LocalBackendModifyDNOperation modifyDNOperation =
             new LocalBackendModifyDNOperation((ModifyDNOperation) operation);
        modifyDNOperation.processLocalModifyDN(backend);
        modifyDNOperation.processLocalModifyDN(this);
        break;
      case COMPARE:
        LocalBackendCompareOperation compareOperation =
             new LocalBackendCompareOperation((CompareOperation) operation);
        compareOperation.processLocalCompare(backend);
        compareOperation.processLocalCompare(this);
        break;
      case ABANDON:
@@ -456,5 +468,58 @@
                                  newAttachment);
  }
  /**
   * Gets the backend associated with this local backend workflow
   * element.
   *
   * @return The backend associated with this local backend workflow
   *         element.
   */
  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()
    {
      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;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java
@@ -54,7 +54,6 @@
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.types.AbstractOperation;
import org.opends.server.types.Attributes;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.CancelRequest;
@@ -65,6 +64,7 @@
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.Operation;
import org.opends.server.types.RawAttribute;
import org.opends.server.types.RawModification;
import org.opends.server.types.RDN;
@@ -1074,7 +1074,7 @@
  {
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    Collection<AbstractOperation> opList = conn.getOperationsInProgress();
    Collection<Operation> opList = conn.getOperationsInProgress();
    assertNotNull(opList);
    assertTrue(opList.isEmpty());
  }