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

matthew_swift
10.41.2008 218b40d6e175f5b58b89ff7e0b3050577d3aff2f
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,206 +27,219 @@
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;
    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;
    if (!isCancelled)
    {
      isCancelled = true;
      // The persistent search can no longer be cancelled.
      searchOperation.getClientConnection().deregisterPersistentSearch(this);
      // Notify any cancellation callbacks.
      for (CancellationCallback callback : cancellationCallbacks)
      {
        try
        {
          callback.persistentSearchCancelled(this);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      }
    }
    return new CancelResult(ResultCode.CANCELED, null);
  }
  /**
   * Retrieves the set of change types for this persistent search.
   * Gets the message ID associated with this persistent search.
   *
   * @return  The set of change types for this persistent search.
   * @return The message ID associated with this persistent search.
   */
  public Set<PersistentSearchChangeType> getChangeTypes()
  public int getMessageID()
  {
    return changeTypes;
    return searchOperation.getMessageID();
  }
  /**
   * Retrieves the returnECs flag for this persistent search.
   * Notifies the persistent searches that an entry has been added.
   *
   * @return  The return ECs flag for this persistent search.
   * @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 boolean getReturnECs()
  {
    return returnECs;
  }
  /**
   * Retrieves the base DN for this persistent search.
   *
   * @return  The base DN for this persistent search.
   */
  public DN getBaseDN()
  {
    return baseDN;
  }
  /**
   * Retrieves the scope for this persistent search.
   *
   * @return  The scope for this persistent search.
   */
  public SearchScope getScope()
  {
    return scope;
  }
  /**
   * Retrieves the filter for this persistent search.
   *
   * @return  The filter for this persistent search.
   */
  public SearchFilter getFilter()
  {
    return filter;
  }
  /**
   * Performs any necessary processing for the provided add operation.
   *
   * @param  addOperation  The add operation that has been processed.
   * @param  entry         The entry that was added.
   */
  public void processAdd(LocalBackendAddOperation addOperation, Entry entry)
  public void processAdd(Entry entry, long changeNumber)
  {
    // See if we care about add operations.
    if (! changeTypes.contains(PersistentSearchChangeType.ADD))
    if (!changeTypes.contains(PersistentSearchChangeType.ADD))
    {
      return;
    }
    // Make sure that the entry is within our target scope.
    switch (scope)
    {
      case BASE_OBJECT:
        if (! baseDN.equals(entry.getDN()))
        {
          return;
        }
        break;
      case SINGLE_LEVEL:
        if (! baseDN.equals(entry.getDN().getParentDNInSuffix()))
        {
          return;
        }
        break;
      case WHOLE_SUBTREE:
        if (! baseDN.isAncestorOf(entry.getDN()))
        {
          return;
        }
        break;
      case SUBORDINATE_SUBTREE:
        if (baseDN.equals(entry.getDN()) ||
            (! baseDN.isAncestorOf(entry.getDN())))
        {
          return;
        }
        break;
      default:
    case BASE_OBJECT:
      if (!baseDN.equals(entry.getDN()))
      {
        return;
      }
      break;
    case SINGLE_LEVEL:
      if (!baseDN.equals(entry.getDN().getParentDNInSuffix()))
      {
        return;
      }
      break;
    case WHOLE_SUBTREE:
      if (!baseDN.isAncestorOf(entry.getDN()))
      {
        return;
      }
      break;
    case SUBORDINATE_SUBTREE:
      if (baseDN.equals(entry.getDN()) || (!baseDN.isAncestorOf(entry.getDN())))
      {
        return;
      }
      break;
    default:
      return;
    }
    // Make sure that the entry matches the target filter.
    try
    {
      if (! filter.matchesEntry(entry))
      if (!filter.matchesEntry(entry))
      {
        return;
      }
@@ -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))
      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,58 +301,58 @@
  /**
   * 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))
    if (!changeTypes.contains(PersistentSearchChangeType.DELETE))
    {
      return;
    }
    // Make sure that the entry is within our target scope.
    switch (scope)
    {
      case BASE_OBJECT:
        if (! baseDN.equals(entry.getDN()))
        {
          return;
        }
        break;
      case SINGLE_LEVEL:
        if (! baseDN.equals(entry.getDN().getParentDNInSuffix()))
        {
          return;
        }
        break;
      case WHOLE_SUBTREE:
        if (! baseDN.isAncestorOf(entry.getDN()))
        {
          return;
        }
        break;
      case SUBORDINATE_SUBTREE:
        if (baseDN.equals(entry.getDN()) ||
            (! baseDN.isAncestorOf(entry.getDN())))
        {
          return;
        }
        break;
      default:
    case BASE_OBJECT:
      if (!baseDN.equals(entry.getDN()))
      {
        return;
      }
      break;
    case SINGLE_LEVEL:
      if (!baseDN.equals(entry.getDN().getParentDNInSuffix()))
      {
        return;
      }
      break;
    case WHOLE_SUBTREE:
      if (!baseDN.isAncestorOf(entry.getDN()))
      {
        return;
      }
      break;
    case SUBORDINATE_SUBTREE:
      if (baseDN.equals(entry.getDN()) || (!baseDN.isAncestorOf(entry.getDN())))
      {
        return;
      }
      break;
    default:
      return;
    }
    // Make sure that the entry matches the target filter.
    try
    {
      if (! filter.matchesEntry(entry))
      if (!filter.matchesEntry(entry))
      {
        return;
      }
@@ -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))
      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,61 +414,78 @@
  /**
   * 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))
    if (!changeTypes.contains(PersistentSearchChangeType.MODIFY))
    {
      return;
    }
    // Make sure that the entry is within our target scope.
    switch (scope)
    {
      case BASE_OBJECT:
        if (! baseDN.equals(oldEntry.getDN()))
        {
          return;
        }
        break;
      case SINGLE_LEVEL:
        if (! baseDN.equals(oldEntry.getDN().getParentDNInSuffix()))
        {
          return;
        }
        break;
      case WHOLE_SUBTREE:
        if (! baseDN.isAncestorOf(oldEntry.getDN()))
        {
          return;
        }
        break;
      case SUBORDINATE_SUBTREE:
        if (baseDN.equals(oldEntry.getDN()) ||
            (! baseDN.isAncestorOf(oldEntry.getDN())))
        {
          return;
        }
        break;
      default:
    case BASE_OBJECT:
      if (!baseDN.equals(oldEntry.getDN()))
      {
        return;
      }
      break;
    case SINGLE_LEVEL:
      if (!baseDN.equals(oldEntry.getDN().getParent()))
      {
        return;
      }
      break;
    case WHOLE_SUBTREE:
      if (!baseDN.isAncestorOf(oldEntry.getDN()))
      {
        return;
      }
      break;
    case SUBORDINATE_SUBTREE:
      if (baseDN.equals(oldEntry.getDN())
          || (!baseDN.isAncestorOf(oldEntry.getDN())))
      {
        return;
      }
      break;
    default:
      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,82 +547,83 @@
  /**
   * 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))
    if (!changeTypes.contains(PersistentSearchChangeType.MODIFY_DN))
    {
      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());
    case BASE_OBJECT:
      oldMatches = baseDN.equals(oldDN);
      newMatches = baseDN.equals(entry.getDN());
        if (! (oldMatches || newMatches))
        {
          return;
        }
        break;
      case SINGLE_LEVEL:
        oldMatches = baseDN.equals(oldEntry.getDN().getParentDNInSuffix());
        newMatches = baseDN.equals(newEntry.getDN().getParentDNInSuffix());
        if (! (oldMatches || newMatches))
        {
          return;
        }
        break;
      case WHOLE_SUBTREE:
        oldMatches = baseDN.isAncestorOf(oldEntry.getDN());
        newMatches = baseDN.isAncestorOf(newEntry.getDN());
        if (! (oldMatches || newMatches))
        {
          return;
        }
        break;
      case SUBORDINATE_SUBTREE:
        oldMatches = ((! baseDN.equals(oldEntry.getDN())) &&
                      baseDN.isAncestorOf(oldEntry.getDN()));
        newMatches = ((! baseDN.equals(newEntry.getDN())) &&
                      baseDN.isAncestorOf(newEntry.getDN()));
        if (! (oldMatches || newMatches))
        {
          return;
        }
        break;
      default:
      if (!(oldMatches || newMatches))
      {
        return;
    }
      }
      break;
    case SINGLE_LEVEL:
      oldMatches = baseDN.equals(oldDN.getParent());
      newMatches = baseDN.equals(entry.getDN().getParent());
      if (!(oldMatches || newMatches))
      {
        return;
      }
      break;
    case WHOLE_SUBTREE:
      oldMatches = baseDN.isAncestorOf(oldDN);
      newMatches = baseDN.isAncestorOf(entry.getDN());
      if (!(oldMatches || newMatches))
      {
        return;
      }
      break;
    case SUBORDINATE_SUBTREE:
      oldMatches = ((!baseDN.equals(oldDN)) && baseDN.isAncestorOf(oldDN));
      newMatches = ((!baseDN.equals(entry.getDN())) && baseDN
          .isAncestorOf(entry.getDN()));
      if (!(oldMatches || newMatches))
      {
        return;
      }
      break;
    default:
      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.
   * @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,11 +1328,11 @@
    {
      try
      {
        for (AbstractOperation o : operationsInProgress.values())
        for (Operation o : operationsInProgress.values())
        {
          try
          {
              o.abort(cancelRequest);
            o.abort(cancelRequest);
            // TODO: Assume its cancelled?
            if (keepStats)
@@ -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,29 +774,41 @@
      }
    }
    // 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()
      {
        try
        public void run()
        {
          changeListener.handleAddOperation(this, entry);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          // Notify persistent searches.
          for (PersistentSearch psearch : wfe.getPersistentSearches()) {
            psearch.processAdd(entry, getChangeNumber());
          }
          logError(ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER.get(
                        getExceptionMessage(e)));
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
          {
            try
            {
              changeListener.handleAddOperation(LocalBackendAddOperation.this,
                  entry);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, 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,29 +445,43 @@
    }
    // 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()
      {
        try
        public void run()
        {
          changeListener.handleDeleteOperation(this, entry);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          // Notify persistent searches.
          for (PersistentSearch psearch : wfe.getPersistentSearches()) {
            psearch.processDelete(entry, getChangeNumber());
          }
          Message message = ERR_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
          logError(message);
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
          {
            try
            {
              changeListener.handleDeleteOperation(
                  LocalBackendDeleteOperation.this, entry);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, 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,30 +633,45 @@
      }
    }
    // 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()
      {
        try
        public void run()
        {
          changeListener.handleModifyDNOperation(this, currentEntry, newEntry);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          // Notify persistent searches.
          for (PersistentSearch psearch : wfe.getPersistentSearches())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
            psearch.processModifyDN(newEntry, getChangeNumber(),
                currentEntry.getDN());
          }
          Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
          logError(message);
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
          {
            try
            {
              changeListener.handleModifyDNOperation(
                  LocalBackendModifyDNOperation.this, currentEntry, newEntry);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, 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());
  }