From 218b40d6e175f5b58b89ff7e0b3050577d3aff2f Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Mon, 10 Nov 2008 13:41:49 +0000
Subject: [PATCH] This change fixes issue 3567:

---
 opends/src/server/org/opends/server/core/PersistentSearch.java |  630 ++++++++++++++++++++++++++++++---------------------------
 1 files changed, 332 insertions(+), 298 deletions(-)

diff --git a/opends/src/server/org/opends/server/core/PersistentSearch.java b/opends/src/server/org/opends/server/core/PersistentSearch.java
index 5e33329..a40bd0f 100644
--- a/opends/src/server/org/opends/server/core/PersistentSearch.java
+++ b/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("\")");
   }
 }
-

--
Gitblit v1.10.0