From fbda6e0892dcfcc8dd43d21f6fb134aabb8d0cac Mon Sep 17 00:00:00 2001
From: jarnou <jarnou@localhost>
Date: Tue, 03 Jul 2007 09:29:17 +0000
Subject: [PATCH] Commits the refactoring of the core server to provide support for proxy/distribution/virtual functionnalities. This includes the new set of local operations, as well as the workflow and networkgroup support.

---
 opends/src/server/org/opends/server/core/SearchOperation.java | 2310 +++++------------------------------------------------------
 1 files changed, 197 insertions(+), 2,113 deletions(-)

diff --git a/opends/src/server/org/opends/server/core/SearchOperation.java b/opends/src/server/org/opends/server/core/SearchOperation.java
index 007fd1d..80fb4fa 100644
--- a/opends/src/server/org/opends/server/core/SearchOperation.java
+++ b/opends/src/server/org/opends/server/core/SearchOperation.java
@@ -26,365 +26,29 @@
  */
 package org.opends.server.core;
 
-
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.opends.server.api.Backend;
-import org.opends.server.api.ClientConnection;
-import org.opends.server.api.plugin.PostOperationPluginResult;
-import org.opends.server.api.plugin.PreOperationPluginResult;
-import org.opends.server.api.plugin.PreParsePluginResult;
-import org.opends.server.api.plugin.SearchEntryPluginResult;
-import org.opends.server.api.plugin.SearchReferencePluginResult;
-import org.opends.server.controls.*;
-import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.protocols.ldap.LDAPFilter;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.AttributeValue;
+import org.opends.server.controls.MatchedValuesControl;
 import org.opends.server.types.ByteString;
-import org.opends.server.types.CancelledOperationException;
-import org.opends.server.types.CancelRequest;
-import org.opends.server.types.CancelResult;
 import org.opends.server.types.Control;
-import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DN;
 import org.opends.server.types.DereferencePolicy;
 import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DisconnectReason;
-import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
-import org.opends.server.types.FilterType;
-import org.opends.server.types.LDAPException;
 import org.opends.server.types.Operation;
-import org.opends.server.types.OperationType;
-import org.opends.server.types.Privilege;
 import org.opends.server.types.RawFilter;
-import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchResultReference;
 import org.opends.server.types.SearchScope;
-import org.opends.server.types.operation.PostOperationSearchOperation;
-import org.opends.server.types.operation.PostResponseSearchOperation;
-import org.opends.server.types.operation.PreOperationSearchOperation;
-import org.opends.server.types.operation.PreParseSearchOperation;
-import org.opends.server.types.operation.SearchEntrySearchOperation;
-import org.opends.server.types.operation.SearchReferenceSearchOperation;
-import org.opends.server.util.TimeThread;
-
-import static org.opends.server.core.CoreConstants.*;
-import static org.opends.server.loggers.AccessLogger.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
-import static org.opends.server.messages.CoreMessages.*;
-import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
-
 
 /**
- * This class defines an operation that may be used to locate entries in the
- * Directory Server based on a given set of criteria.
+ * This interface defines an operation used to search for entries
+ * in the Directory Server.
  */
-public class SearchOperation
-       extends Operation
-       implements PreParseSearchOperation, PreOperationSearchOperation,
-                  PostOperationSearchOperation, PostResponseSearchOperation,
-                  SearchEntrySearchOperation, SearchReferenceSearchOperation
+public interface SearchOperation extends Operation
 {
-  /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = getTracer();
-
-  // Indicates whether a search result done response has been sent to the
-  // client.
-  private AtomicBoolean responseSent;
-
-  // Indicates whether the client is able to handle referrals.
-  private boolean clientAcceptsReferrals;
-
-  // Indicates whether to include the account usable control with search result
-  // entries.
-  private boolean includeUsableControl;
-
-  // Indicates whether to only real attributes should be returned.
-  private boolean realAttributesOnly;
-
-  // Indicates whether LDAP subentries should be returned.
-  private boolean returnLDAPSubentries;
-
-  // Indicates whether to include attribute types only or both types and values.
-  private boolean typesOnly;
-
-  // Indicates whether to only virtual attributes should be returned.
-  private boolean virtualAttributesOnly;
-
-  // The raw, unprocessed base DN as included in the request from the client.
-  private ByteString rawBaseDN;
-
-  // The cancel request that has been issued for this search operation.
-  private CancelRequest cancelRequest;
-
-  // The dereferencing policy for the search operation.
-  private DereferencePolicy derefPolicy;
-
-  // The base DN for the search operation.
-  private DN baseDN;
-
-  // The proxied authorization target DN for this operation.
-  private DN proxiedAuthorizationDN;
-
-  // The number of entries that have been sent to the client.
-  private int entriesSent;
-
-  // The number of search result references that have been sent to the client.
-  private int referencesSent;
-
-  // The size limit for the search operation.
-  private int sizeLimit;
-
-  // The time limit for the search operation.
-  private int timeLimit;
-
-  // The set of attributes that should be returned in matching entries.
-  private LinkedHashSet<String> attributes;
-
-  // The set of response controls for this search operation.
-  private List<Control> responseControls;
-
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
-  // The time that the search time limit has expired.
-  private long timeLimitExpiration;
-
-  // The matched values control associated with this search operation.
-  private MatchedValuesControl matchedValuesControl;
-
-  // The persistent search associated with this search operation.
-  private PersistentSearch persistentSearch;
-
-  // The raw, unprocessed filter as included in the request from the client.
-  private RawFilter rawFilter;
-
-  // The search filter for the search operation.
-  private SearchFilter filter;
-
-  // The search scope for the search operation.
-  private SearchScope scope;
-
-
-
-  /**
-   * Creates a new search operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  rawBaseDN         The raw, unprocessed base DN as included in the
-   *                           request from the client.
-   * @param  scope             The scope for this search operation.
-   * @param  derefPolicy       The alias dereferencing policy for this search
-   *                           operation.
-   * @param  sizeLimit         The size limit for this search operation.
-   * @param  timeLimit         The time limit for this search operation.
-   * @param  typesOnly         The typesOnly flag for this search operation.
-   * @param  rawFilter         the raw, unprocessed filter as included in the
-   *                           request from the client.
-   * @param  attributes        The requested attributes for this search
-   *                           operation.
-   */
-  public SearchOperation(ClientConnection clientConnection, long operationID,
-                         int messageID, List<Control> requestControls,
-                         ByteString rawBaseDN, SearchScope scope,
-                         DereferencePolicy derefPolicy, int sizeLimit,
-                         int timeLimit, boolean typesOnly, RawFilter rawFilter,
-                         LinkedHashSet<String> attributes)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.rawBaseDN   = rawBaseDN;
-    this.scope       = scope;
-    this.derefPolicy = derefPolicy;
-    this.sizeLimit   = sizeLimit;
-    this.timeLimit   = timeLimit;
-    this.typesOnly   = typesOnly;
-    this.rawFilter   = rawFilter;
-
-    if (attributes == null)
-    {
-      this.attributes  = new LinkedHashSet<String>(0);
-    }
-    else
-    {
-      this.attributes  = attributes;
-    }
-
-
-    if (clientConnection.getSizeLimit() <= 0)
-    {
-      this.sizeLimit = sizeLimit;
-    }
-    else
-    {
-      if (sizeLimit <= 0)
-      {
-        this.sizeLimit = clientConnection.getSizeLimit();
-      }
-      else
-      {
-        this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit());
-      }
-    }
-
-
-    if (clientConnection.getTimeLimit() <= 0)
-    {
-      this.timeLimit = timeLimit;
-    }
-    else
-    {
-      if (timeLimit <= 0)
-      {
-        this.timeLimit = clientConnection.getTimeLimit();
-      }
-      else
-      {
-        this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit());
-      }
-    }
-
-
-    baseDN                 = null;
-    filter                 = null;
-    entriesSent            = 0;
-    referencesSent         = 0;
-    responseControls       = new ArrayList<Control>();
-    cancelRequest          = null;
-    clientAcceptsReferrals = true;
-    includeUsableControl   = false;
-    responseSent           = new AtomicBoolean(false);
-    persistentSearch       = null;
-    returnLDAPSubentries   = false;
-    matchedValuesControl   = null;
-    realAttributesOnly     = false;
-    virtualAttributesOnly  = false;
-  }
-
-
-
-  /**
-   * Creates a new search operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  baseDN            The base DN for this search operation.
-   * @param  scope             The scope for this search operation.
-   * @param  derefPolicy       The alias dereferencing policy for this search
-   *                           operation.
-   * @param  sizeLimit         The size limit for this search operation.
-   * @param  timeLimit         The time limit for this search operation.
-   * @param  typesOnly         The typesOnly flag for this search operation.
-   * @param  filter            The filter for this search operation.
-   * @param  attributes        The attributes for this search operation.
-   */
-  public SearchOperation(ClientConnection clientConnection, long operationID,
-                         int messageID, List<Control> requestControls,
-                         DN baseDN, SearchScope scope,
-                         DereferencePolicy derefPolicy, int sizeLimit,
-                         int timeLimit, boolean typesOnly, SearchFilter filter,
-                         LinkedHashSet<String> attributes)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.baseDN      = baseDN;
-    this.scope       = scope;
-    this.derefPolicy = derefPolicy;
-    this.sizeLimit   = sizeLimit;
-    this.timeLimit   = timeLimit;
-    this.typesOnly   = typesOnly;
-    this.filter      = filter;
-
-    if (attributes == null)
-    {
-      this.attributes = new LinkedHashSet<String>(0);
-    }
-    else
-    {
-      this.attributes  = attributes;
-    }
-
-    rawBaseDN = new ASN1OctetString(baseDN.toString());
-    rawFilter = new LDAPFilter(filter);
-
-
-    if (clientConnection.getSizeLimit() <= 0)
-    {
-      this.sizeLimit = sizeLimit;
-    }
-    else
-    {
-      if (sizeLimit <= 0)
-      {
-        this.sizeLimit = clientConnection.getSizeLimit();
-      }
-      else
-      {
-        this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit());
-      }
-    }
-
-
-    if (clientConnection.getTimeLimit() <= 0)
-    {
-      this.timeLimit = timeLimit;
-    }
-    else
-    {
-      if (timeLimit <= 0)
-      {
-        this.timeLimit = clientConnection.getTimeLimit();
-      }
-      else
-      {
-        this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit());
-      }
-    }
-
-
-    entriesSent            = 0;
-    referencesSent         = 0;
-    responseControls       = new ArrayList<Control>();
-    cancelRequest          = null;
-    clientAcceptsReferrals = true;
-    includeUsableControl   = false;
-    responseSent           = new AtomicBoolean(false);
-    persistentSearch       = null;
-    returnLDAPSubentries   = false;
-    matchedValuesControl   = null;
-  }
-
-
 
   /**
    * Retrieves the raw, unprocessed base DN as included in the request from the
@@ -394,12 +58,7 @@
    * @return  The raw, unprocessed base DN as included in the request from the
    *          client.
    */
-  public final ByteString getRawBaseDN()
-  {
-    return rawBaseDN;
-  }
-
-
+  public abstract ByteString getRawBaseDN();
 
   /**
    * Specifies the raw, unprocessed base DN as included in the request from the
@@ -408,14 +67,7 @@
    * @param  rawBaseDN  The raw, unprocessed base DN as included in the request
    *                    from the client.
    */
-  public final void setRawBaseDN(ByteString rawBaseDN)
-  {
-    this.rawBaseDN = rawBaseDN;
-
-    baseDN = null;
-  }
-
-
+  public abstract void setRawBaseDN(ByteString rawBaseDN);
 
   /**
    * Retrieves the base DN for this search operation.  This should not be called
@@ -425,12 +77,7 @@
    * @return  The base DN for this search operation, or <CODE>null</CODE> if the
    *          raw base DN has not yet been processed.
    */
-  public final DN getBaseDN()
-  {
-    return baseDN;
-  }
-
-
+  public abstract DN getBaseDN();
 
   /**
    * Specifies the base DN for this search operation.  This method is only
@@ -438,24 +85,14 @@
    *
    * @param  baseDN  The base DN for this search operation.
    */
-  public final void setBaseDN(DN baseDN)
-  {
-    this.baseDN = baseDN;
-  }
-
-
+  public abstract void setBaseDN(DN baseDN);
 
   /**
    * Retrieves the scope for this search operation.
    *
    * @return  The scope for this search operation.
    */
-  public final SearchScope getScope()
-  {
-    return scope;
-  }
-
-
+  public abstract SearchScope getScope();
 
   /**
    * Specifies the scope for this search operation.  This should only be called
@@ -463,24 +100,14 @@
    *
    * @param  scope  The scope for this search operation.
    */
-  public final void setScope(SearchScope scope)
-  {
-    this.scope = scope;
-  }
-
-
+  public abstract void setScope(SearchScope scope);
 
   /**
    * Retrieves the alias dereferencing policy for this search operation.
    *
    * @return  The alias dereferencing policy for this search operation.
    */
-  public final DereferencePolicy getDerefPolicy()
-  {
-    return derefPolicy;
-  }
-
-
+  public abstract DereferencePolicy getDerefPolicy();
 
   /**
    * Specifies the alias dereferencing policy for this search operation.  This
@@ -489,24 +116,14 @@
    * @param  derefPolicy  The alias dereferencing policy for this search
    *                      operation.
    */
-  public final void setDerefPolicy(DereferencePolicy derefPolicy)
-  {
-    this.derefPolicy = derefPolicy;
-  }
-
-
+  public abstract void setDerefPolicy(DereferencePolicy derefPolicy);
 
   /**
    * Retrieves the size limit for this search operation.
    *
    * @return  The size limit for this search operation.
    */
-  public final int getSizeLimit()
-  {
-    return sizeLimit;
-  }
-
-
+  public abstract int getSizeLimit();
 
   /**
    * Specifies the size limit for this search operation.  This should only be
@@ -514,24 +131,21 @@
    *
    * @param  sizeLimit  The size limit for this search operation.
    */
-  public final void setSizeLimit(int sizeLimit)
-  {
-    this.sizeLimit = sizeLimit;
-  }
-
-
+  public abstract void setSizeLimit(int sizeLimit);
 
   /**
    * Retrieves the time limit for this search operation.
    *
    * @return  The time limit for this search operation.
    */
-  public final int getTimeLimit()
-  {
-    return timeLimit;
-  }
+  public abstract int getTimeLimit();
 
-
+  /**
+   * Get the time after which the search time limit has expired.
+   *
+   * @return the timeLimitExpiration
+   */
+  public abstract Long getTimeLimitExpiration();
 
   /**
    * Specifies the time limit for this search operation.  This should only be
@@ -539,24 +153,14 @@
    *
    * @param  timeLimit  The time limit for this search operation.
    */
-  public final void setTimeLimit(int timeLimit)
-  {
-    this.timeLimit = timeLimit;
-  }
-
-
+  public abstract void setTimeLimit(int timeLimit);
 
   /**
    * Retrieves the typesOnly flag for this search operation.
    *
    * @return  The typesOnly flag for this search operation.
    */
-  public final boolean getTypesOnly()
-  {
-    return typesOnly;
-  }
-
-
+  public abstract boolean getTypesOnly();
 
   /**
    * Specifies the typesOnly flag for this search operation.  This should only
@@ -564,12 +168,7 @@
    *
    * @param  typesOnly  The typesOnly flag for this search operation.
    */
-  public final void setTypesOnly(boolean typesOnly)
-  {
-    this.typesOnly = typesOnly;
-  }
-
-
+  public abstract void setTypesOnly(boolean typesOnly);
 
   /**
    * Retrieves the raw, unprocessed search filter as included in the request
@@ -580,12 +179,7 @@
    * @return  The raw, unprocessed search filter as included in the request from
    *          the client.
    */
-  public final RawFilter getRawFilter()
-  {
-    return rawFilter;
-  }
-
-
+  public abstract RawFilter getRawFilter();
 
   /**
    * Specifies the raw, unprocessed search filter as included in the request
@@ -594,14 +188,7 @@
    * @param  rawFilter  The raw, unprocessed search filter as included in the
    *                    request from the client.
    */
-  public final void setRawFilter(RawFilter rawFilter)
-  {
-    this.rawFilter = rawFilter;
-
-    filter = null;
-  }
-
-
+  public abstract void setRawFilter(RawFilter rawFilter);
 
   /**
    * Retrieves the filter for this search operation.  This should not be called
@@ -611,12 +198,7 @@
    * @return  The filter for this search operation, or <CODE>null</CODE> if the
    *          raw filter has not yet been processed.
    */
-  public final SearchFilter getFilter()
-  {
-    return filter;
-  }
-
-
+  public abstract SearchFilter getFilter();
 
   /**
    * Retrieves the set of requested attributes for this search operation.  Its
@@ -624,12 +206,7 @@
    *
    * @return  The set of requested attributes for this search operation.
    */
-  public final LinkedHashSet<String> getAttributes()
-  {
-    return attributes;
-  }
-
-
+  public abstract LinkedHashSet<String> getAttributes();
 
   /**
    * Specifies the set of requested attributes for this search operation.  It
@@ -638,19 +215,7 @@
    * @param  attributes  The set of requested attributes for this search
    *                     operation.
    */
-  public final void setAttributes(LinkedHashSet<String> attributes)
-  {
-    if (attributes == null)
-    {
-      this.attributes.clear();
-    }
-    else
-    {
-      this.attributes = attributes;
-    }
-  }
-
-
+  public abstract void setAttributes(LinkedHashSet<String> attributes);
 
   /**
    * Retrieves the number of entries sent to the client for this search
@@ -659,12 +224,7 @@
    * @return  The number of entries sent to the client for this search
    *          operation.
    */
-  public final int getEntriesSent()
-  {
-    return entriesSent;
-  }
-
-
+  public abstract int getEntriesSent();
 
   /**
    * Retrieves the number of search references sent to the client for this
@@ -673,45 +233,7 @@
    * @return  The number of search references sent to the client for this search
    *          operation.
    */
-  public final int getReferencesSent()
-  {
-    return referencesSent;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
+  public abstract int getReferencesSent();
 
   /**
    * Used as a callback for backends to indicate that the provided entry matches
@@ -728,439 +250,7 @@
    *          <CODE>false</CODE> if not for some reason (e.g., the size limit
    *          has been reached or the search has been abandoned).
    */
-  public final boolean returnEntry(Entry entry, List<Control> controls)
-  {
-    // See if the operation has been abandoned.  If so, then don't send the
-    // entry and indicate that the search should end.
-    if (cancelRequest != null)
-    {
-      setResultCode(ResultCode.CANCELED);
-      return false;
-    }
-
-    // See if the size limit has been exceeded.  If so, then don't send the
-    // entry and indicate that the search should end.
-    if ((sizeLimit > 0) && (entriesSent >= sizeLimit))
-    {
-      setResultCode(ResultCode.SIZE_LIMIT_EXCEEDED);
-      appendErrorMessage(getMessage(MSGID_SEARCH_SIZE_LIMIT_EXCEEDED,
-                                    sizeLimit));
-      return false;
-    }
-
-    // See if the time limit has expired.  If so, then don't send the entry and
-    // indicate that the search should end.
-    if ((timeLimit > 0) && (TimeThread.getTime() >= timeLimitExpiration))
-    {
-      setResultCode(ResultCode.TIME_LIMIT_EXCEEDED);
-      appendErrorMessage(getMessage(MSGID_SEARCH_TIME_LIMIT_EXCEEDED,
-                                    timeLimit));
-      return false;
-    }
-
-    // Determine whether the provided entry is a subentry and if so whether it
-    // should be returned.
-    if ((scope != SearchScope.BASE_OBJECT) && (! returnLDAPSubentries) &&
-        entry.isLDAPSubentry())
-    {
-      // Check to see if the filter contains an equality element with the
-      // objectclass attribute type and a value of "ldapSubentry".  If so, then
-      // we'll return it anyway.  Technically, this isn't part of the
-      // specification so we don't need to get carried away with really in-depth
-      // checks.
-      switch (filter.getFilterType())
-      {
-        case AND:
-        case OR:
-          for (SearchFilter f : filter.getFilterComponents())
-          {
-            if ((f.getFilterType() == FilterType.EQUALITY) &&
-                (f.getAttributeType().isObjectClassType()))
-            {
-              AttributeValue v = f.getAssertionValue();
-              if (toLowerCase(v.getStringValue()).equals("ldapsubentry"))
-              {
-                returnLDAPSubentries = true;
-              }
-              break;
-            }
-          }
-          break;
-        case EQUALITY:
-          AttributeType t = filter.getAttributeType();
-          if (t.isObjectClassType())
-          {
-            AttributeValue v = filter.getAssertionValue();
-            if (toLowerCase(v.getStringValue()).equals("ldapsubentry"))
-            {
-              returnLDAPSubentries = true;
-            }
-          }
-          break;
-      }
-
-      if (! returnLDAPSubentries)
-      {
-        // We still shouldn't return it even based on the filter.  Just throw it
-        // away without doing anything.
-        return true;
-      }
-    }
-
-
-    // Determine whether to include the account usable control.  If so, then
-    // create it now.
-    if (includeUsableControl)
-    {
-      try
-      {
-        // FIXME -- Need a way to enable PWP debugging.
-        PasswordPolicyState pwpState = new PasswordPolicyState(entry, false,
-                                                               false);
-
-        boolean isInactive           = pwpState.isDisabled() ||
-                                       pwpState.isAccountExpired();
-        boolean isLocked             = pwpState.lockedDueToFailures() ||
-                                       pwpState.lockedDueToMaximumResetAge() ||
-                                       pwpState.lockedDueToIdleInterval();
-        boolean isReset              = pwpState.mustChangePassword();
-        boolean isExpired            = pwpState.isPasswordExpired();
-
-        if (isInactive || isLocked || isReset || isExpired)
-        {
-          int secondsBeforeUnlock  = pwpState.getSecondsUntilUnlock();
-          int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
-
-          if (controls == null)
-          {
-            controls = new ArrayList<Control>(1);
-          }
-
-          controls.add(new AccountUsableResponseControl(isInactive, isReset,
-                                isExpired, remainingGraceLogins, isLocked,
-                                secondsBeforeUnlock));
-        }
-        else
-        {
-          if (controls == null)
-          {
-            controls = new ArrayList<Control>(1);
-          }
-
-          int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration();
-          controls.add(new AccountUsableResponseControl(
-                                secondsBeforeExpiration));
-        }
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-      }
-    }
-
-    // Check to see if the entry can be read by the client.
-    SearchResultEntry tmpSearchEntry = new SearchResultEntry(entry,
-        controls);
-    if (AccessControlConfigManager.getInstance()
-        .getAccessControlHandler().maySend(this, tmpSearchEntry) == false) {
-      return true;
-    }
-
-    // Make a copy of the entry and pare it down to only include the set
-    // of
-    // requested attributes.
-    Entry entryToReturn;
-    if ((attributes == null) || attributes.isEmpty())
-    {
-      entryToReturn = entry.duplicateWithoutOperationalAttributes(typesOnly,
-                                                                  true);
-    }
-    else
-    {
-      entryToReturn = entry.duplicateWithoutAttributes();
-
-      for (String attrName : attributes)
-      {
-        if (attrName.equals("*"))
-        {
-          // This is a special placeholder indicating that all user attributes
-          // should be returned.
-          if (typesOnly)
-          {
-            // First, add the placeholder for the objectclass attribute.
-            AttributeType ocType =
-                 DirectoryServer.getObjectClassAttributeType();
-            List<Attribute> ocList = new ArrayList<Attribute>(1);
-            ocList.add(new Attribute(ocType));
-            entryToReturn.putAttribute(ocType, ocList);
-          }
-          else
-          {
-            // First, add the objectclass attribute.
-            Attribute ocAttr = entry.getObjectClassAttribute();
-            try
-            {
-              entryToReturn.setObjectClasses(ocAttr.getValues());
-            }
-            catch (DirectoryException e)
-            {
-              // We cannot get this exception because the object classes have
-              // already been validated in the entry they came from.
-            }
-          }
-
-          // Next iterate through all the user attributes and include them.
-          for (AttributeType t : entry.getUserAttributes().keySet())
-          {
-            List<Attribute> attrList =
-                 entry.duplicateUserAttribute(t, null, typesOnly);
-            entryToReturn.putAttribute(t, attrList);
-          }
-
-          continue;
-        }
-        else if (attrName.equals("+"))
-        {
-          // This is a special placeholder indicating that all operational
-          // attributes should be returned.
-          for (AttributeType t : entry.getOperationalAttributes().keySet())
-          {
-            List<Attribute> attrList =
-                 entry.duplicateOperationalAttribute(t, null, typesOnly);
-            entryToReturn.putAttribute(t, attrList);
-          }
-
-          continue;
-        }
-
-        String lowerName;
-        HashSet<String> options;
-        int semicolonPos = attrName.indexOf(';');
-        if (semicolonPos > 0)
-        {
-          lowerName = toLowerCase(attrName.substring(0, semicolonPos));
-          int nextPos = attrName.indexOf(';', semicolonPos+1);
-          options = new HashSet<String>();
-          while (nextPos > 0)
-          {
-            options.add(attrName.substring(semicolonPos+1, nextPos));
-
-            semicolonPos = nextPos;
-            nextPos = attrName.indexOf(';', semicolonPos+1);
-          }
-
-          options.add(attrName.substring(semicolonPos+1));
-        }
-        else
-        {
-          lowerName = toLowerCase(attrName);
-          options = null;
-        }
-
-
-        AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
-        if (attrType == null)
-        {
-          boolean added = false;
-          for (AttributeType t : entry.getUserAttributes().keySet())
-          {
-            if (t.hasNameOrOID(lowerName))
-            {
-              List<Attribute> attrList =
-                   entry.duplicateUserAttribute(t, options, typesOnly);
-              if (attrList != null)
-              {
-                entryToReturn.putAttribute(t, attrList);
-
-                added = true;
-                break;
-              }
-            }
-          }
-
-          if (added)
-          {
-            continue;
-          }
-
-          for (AttributeType t : entry.getOperationalAttributes().keySet())
-          {
-            if (t.hasNameOrOID(lowerName))
-            {
-              List<Attribute> attrList =
-                   entry.duplicateOperationalAttribute(t, options, typesOnly);
-              if (attrList != null)
-              {
-                entryToReturn.putAttribute(t, attrList);
-
-                break;
-              }
-            }
-          }
-        }
-        else
-        {
-          if (attrType.isObjectClassType()) {
-            if (typesOnly)
-            {
-              AttributeType ocType =
-                   DirectoryServer.getObjectClassAttributeType();
-              List<Attribute> ocList = new ArrayList<Attribute>(1);
-              ocList.add(new Attribute(ocType));
-              entryToReturn.putAttribute(ocType, ocList);
-            }
-            else
-            {
-              List<Attribute> attrList = new ArrayList<Attribute>(1);
-              attrList.add(entry.getObjectClassAttribute());
-              entryToReturn.putAttribute(attrType, attrList);
-            }
-          }
-          else
-          {
-            List<Attribute> attrList =
-                 entry.duplicateOperationalAttribute(attrType, options,
-                                                     typesOnly);
-            if (attrList == null)
-            {
-              attrList = entry.duplicateUserAttribute(attrType, options,
-                                                      typesOnly);
-            }
-            if (attrList != null)
-            {
-              entryToReturn.putAttribute(attrType, attrList);
-            }
-          }
-        }
-      }
-    }
-
-
-    if (realAttributesOnly)
-    {
-      entryToReturn.stripVirtualAttributes();
-    }
-    else if (virtualAttributesOnly)
-    {
-      entryToReturn.stripRealAttributes();
-    }
-
-
-    // If there is a matched values control, then further pare down the entry
-    // based on the filters that it contains.
-    if ((matchedValuesControl != null) && (! typesOnly))
-    {
-      // First, look at the set of objectclasses.
-      AttributeType attrType = DirectoryServer.getObjectClassAttributeType();
-      Iterator<String> ocIterator =
-           entryToReturn.getObjectClasses().values().iterator();
-      while (ocIterator.hasNext())
-      {
-        String ocName = ocIterator.next();
-        AttributeValue v = new AttributeValue(attrType,
-                                              new ASN1OctetString(ocName));
-        if (! matchedValuesControl.valueMatches(attrType, v))
-        {
-          ocIterator.remove();
-        }
-      }
-
-
-      // Next, the set of user attributes.
-      for (AttributeType t : entryToReturn.getUserAttributes().keySet())
-      {
-        for (Attribute a : entryToReturn.getUserAttribute(t))
-        {
-          Iterator<AttributeValue> valueIterator = a.getValues().iterator();
-          while (valueIterator.hasNext())
-          {
-            AttributeValue v = valueIterator.next();
-            if (! matchedValuesControl.valueMatches(t, v))
-            {
-              valueIterator.remove();
-            }
-          }
-        }
-      }
-
-
-      // Then the set of operational attributes.
-      for (AttributeType t : entryToReturn.getOperationalAttributes().keySet())
-      {
-        for (Attribute a : entryToReturn.getOperationalAttribute(t))
-        {
-          Iterator<AttributeValue> valueIterator = a.getValues().iterator();
-          while (valueIterator.hasNext())
-          {
-            AttributeValue v = valueIterator.next();
-            if (! matchedValuesControl.valueMatches(t, v))
-            {
-              valueIterator.remove();
-            }
-          }
-        }
-      }
-    }
-
-
-    // Convert the provided entry to a search result entry.
-    SearchResultEntry searchEntry = new SearchResultEntry(entryToReturn,
-                                                          controls);
-
-    // Strip out any attributes that the client does not have access to.
-
-    // FIXME: need some way to prevent plugins from adding attributes or
-    // values that the client is not permitted to see.
-    searchEntry = AccessControlConfigManager.getInstance()
-        .getAccessControlHandler().filterEntry(this, searchEntry);
-
-    // Invoke any search entry plugins that may be registered with the server.
-    SearchEntryPluginResult pluginResult =
-         DirectoryServer.getPluginConfigManager().
-              invokeSearchResultEntryPlugins(this, searchEntry);
-    if (pluginResult.connectionTerminated())
-    {
-      // We won't attempt to send this entry, and we won't continue with
-      // any processing.  Just update the operation to indicate that it was
-      // cancelled and return false.
-      setResultCode(ResultCode.CANCELED);
-      appendErrorMessage(getMessage(MSGID_CANCELED_BY_SEARCH_ENTRY_DISCONNECT,
-                                    String.valueOf(entry.getDN())));
-      return false;
-    }
-
-
-    // Send the entry to the client.
-    if (pluginResult.sendEntry())
-    {
-      try
-      {
-        clientConnection.sendSearchEntry(this, searchEntry);
-
-        // Log the entry sent to the client.
-        logSearchResultEntry(this, searchEntry);
-
-        entriesSent++;
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResponseData(de);
-        return false;
-      }
-    }
-
-    return pluginResult.continueSearch();
-  }
-
-
+  public abstract boolean returnEntry(Entry entry, List<Control> controls);
 
   /**
    * Used as a callback for backends to indicate that the provided search
@@ -1174,100 +264,7 @@
    *          <CODE>false</CODE> if not for some reason (e.g., the size limit
    *          has been reached or the search has been abandoned).
    */
-  public final boolean returnReference(SearchResultReference reference)
-  {
-    // See if the operation has been abandoned.  If so, then don't send the
-    // reference and indicate that the search should end.
-    if (cancelRequest != null)
-    {
-      setResultCode(ResultCode.CANCELED);
-      return false;
-    }
-
-
-    // See if the time limit has expired.  If so, then don't send the entry and
-    // indicate that the search should end.
-    if ((timeLimit > 0) && (TimeThread.getTime() >= timeLimitExpiration))
-    {
-      setResultCode(ResultCode.TIME_LIMIT_EXCEEDED);
-      appendErrorMessage(getMessage(MSGID_SEARCH_TIME_LIMIT_EXCEEDED,
-                                    timeLimit));
-      return false;
-    }
-
-
-    // See if we know that this client can't handle referrals.  If so, then
-    // don't even try to send it.
-    if (! clientAcceptsReferrals)
-    {
-      return true;
-    }
-
-
-    // See if the client has permission to read this reference.
-    if (AccessControlConfigManager.getInstance()
-        .getAccessControlHandler().maySend(this, reference) == false) {
-      return true;
-    }
-
-
-    // Invoke any search reference plugins that may be registered with the
-    // server.
-    SearchReferencePluginResult pluginResult =
-         DirectoryServer.getPluginConfigManager().
-              invokeSearchResultReferencePlugins(this, reference);
-    if (pluginResult.connectionTerminated())
-    {
-      // We won't attempt to send this entry, and we won't continue with
-      // any processing.  Just update the operation to indicate that it was
-      // cancelled and return false.
-      setResultCode(ResultCode.CANCELED);
-      appendErrorMessage(getMessage(MSGID_CANCELED_BY_SEARCH_REF_DISCONNECT,
-           String.valueOf(reference.getReferralURLString())));
-      return false;
-    }
-
-
-    // Send the reference to the client.  Note that this could throw an
-    // exception, which would indicate that the associated client can't handle
-    // referrals.  If that't the case, then set a flag so we'll know not to try
-    // to send any more.
-    if (pluginResult.sendReference())
-    {
-      try
-      {
-        if (clientConnection.sendSearchReference(this, reference))
-        {
-          // Log the entry sent to the client.
-          logSearchResultReference(this, reference);
-          referencesSent++;
-
-          // FIXME -- Should the size limit apply here?
-        }
-        else
-        {
-          // We know that the client can't handle referrals, so we won't try to
-          // send it any more.
-          clientAcceptsReferrals = false;
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResponseData(de);
-        return false;
-      }
-    }
-
-
-    return pluginResult.continueSearch();
-  }
-
-
+  public abstract boolean returnReference(SearchResultReference reference);
 
   /**
    * Sends the search result done message to the client.  Note that this method
@@ -1277,174 +274,182 @@
    * message should have been set for this operation before this method is
    * called.
    */
-  public final void sendSearchResultDone()
-  {
-    // Send the search result done message to the client.  We want to make sure
-    // that this only gets sent once, and it's possible that this could be
-    // multithreaded in the event of a persistent search, so do it safely.
-    if (responseSent.compareAndSet(false, true))
-    {
-      // Send the response to the client.
-      clientConnection.sendResponse(this);
-
-      // Log the search result.
-      logSearchResultDone(this);
-
-
-      // Invoke the post-response search plugins.
-      DirectoryServer.getPluginConfigManager().
-           invokePostResponseSearchPlugins(this);
-    }
-  }
-
-
+  public abstract void sendSearchResultDone();
 
   /**
-   * {@inheritDoc}
+   * Set the time after which the search time limit has expired.
+   *
+   * @param timeLimitExpiration - Time after which the search has expired
    */
-  @Override()
-  public final OperationType getOperationType()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    return OperationType.SEARCH;
-  }
-
-
+  public abstract void setTimeLimitExpiration(Long timeLimitExpiration);
 
   /**
-   * {@inheritDoc}
+   * Indicates whether LDAP subentries should be returned or not.
+   *
+   * @return true if the LDAP subentries should be returned, false otherwise
    */
-  @Override()
-  public final void disconnectClient(DisconnectReason disconnectReason,
-                                     boolean sendNotification, String message,
-                                     int messageID)
-  {
-    // Before calling clientConnection.disconnect, we need to mark this
-    // operation as cancelled so that the attempt to cancel it later won't cause
-    // an unnecessary delay.
-    setCancelResult(CancelResult.CANCELED);
-
-    clientConnection.disconnect(disconnectReason, sendNotification, message,
-                                messageID);
-  }
-
-
+  public abstract boolean isReturnLDAPSubentries();
 
   /**
-   * {@inheritDoc}
+   * Set the flag indicating wether the LDAP subentries should be returned.
+   *
+   * @param returnLDAPSubentries - Boolean indicating wether the LDAP
+   *                               subentries should be returned or not
    */
-  @Override()
-  public final String[][] getRequestLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    String attrs;
-    if ((attributes == null) || attributes.isEmpty())
-    {
-      attrs = null;
-    }
-    else
-    {
-      StringBuilder attrBuffer = new StringBuilder();
-      Iterator<String> iterator = attributes.iterator();
-      attrBuffer.append(iterator.next());
-
-      while (iterator.hasNext())
-      {
-        attrBuffer.append(", ");
-        attrBuffer.append(iterator.next());
-      }
-
-      attrs = attrBuffer.toString();
-    }
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_BASE_DN, String.valueOf(rawBaseDN) },
-      new String[] { LOG_ELEMENT_SCOPE, String.valueOf(scope) },
-      new String[] { LOG_ELEMENT_SIZE_LIMIT, String.valueOf(sizeLimit) },
-      new String[] { LOG_ELEMENT_TIME_LIMIT, String.valueOf(timeLimit) },
-      new String[] { LOG_ELEMENT_FILTER, String.valueOf(rawFilter) },
-      new String[] { LOG_ELEMENT_REQUESTED_ATTRIBUTES, attrs }
-    };
-  }
-
-
+  public abstract void setReturnLDAPSubentries(boolean returnLDAPSubentries);
 
   /**
-   * {@inheritDoc}
+   * The matched values control associated with this search operation.
+   *
+   * @return the match values control
    */
-  @Override()
-  public final String[][] getResponseLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
+  public abstract MatchedValuesControl getMatchedValuesControl();
 
-    String resultCode = String.valueOf(getResultCode().getIntValue());
+  /**
+   * Set the match values control.
+   *
+   * @param controls - The matched values control
+   */
+  public abstract void setMatchedValuesControl(MatchedValuesControl controls);
 
-    String errorMessage;
-    StringBuilder errorMessageBuffer = getErrorMessage();
-    if (errorMessageBuffer == null)
-    {
-      errorMessage = null;
-    }
-    else
-    {
-      errorMessage = errorMessageBuffer.toString();
-    }
+  /**
+   * Indicates whether to include the account usable response control with
+   * search result entries or not.
+   *
+   * @return true if the usable control has to be part of the search result
+   *         entry
+   */
+  public abstract boolean isIncludeUsableControl();
 
-    String matchedDNStr;
-    DN matchedDN = getMatchedDN();
-    if (matchedDN == null)
-    {
-      matchedDNStr = null;
-    }
-    else
-    {
-      matchedDNStr = matchedDN.toString();
-    }
+  /**
+   * Specify whether to include the account usable response control within the
+   * search result entries.
+   *
+   * @param includeUsableControl - True if the account usable response control
+   *                               has to be included within the search result
+   *                               entries, false otherwise
+   */
+  public abstract void setIncludeUsableControl(boolean includeUsableControl);
 
-    String referrals;
-    List<String> referralURLs = getReferralURLs();
-    if ((referralURLs == null) || referralURLs.isEmpty())
-    {
-      referrals = null;
-    }
-    else
-    {
-      StringBuilder buffer = new StringBuilder();
-      Iterator<String> iterator = referralURLs.iterator();
-      buffer.append(iterator.next());
+  /**
+   * Register the psearch in the search operation.
+   *
+   * @param psearch - Persistent search associated to that operation
+   */
+  public abstract void setPersistentSearch(PersistentSearch psearch);
 
-      while (iterator.hasNext())
-      {
-        buffer.append(", ");
-        buffer.append(iterator.next());
-      }
+  /**
+   * Get the psearch from the search operation.
+   *
+   * @return the psearch, or null if no psearch was registered
+   */
+  public abstract PersistentSearch getPersistentSearch();
 
-      referrals = buffer.toString();
-    }
+  /**
+   * Indicates whether the client is able to handle referrals.
+   *
+   * @return true, if the client is able to handle referrals
+   */
+  public abstract boolean isClientAcceptsReferrals();
 
-    String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
+  /**
+   * Specify whether the client is able to handle referrals.
+   *
+   * @param clientAcceptReferrals - Boolean set to true if the client
+   *                                can handle referrals
+   */
+  public abstract void setClientAcceptsReferrals(boolean clientAcceptReferrals);
 
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
-      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
-      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
-      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
-      new String[] { LOG_ELEMENT_ENTRIES_SENT, String.valueOf(entriesSent) },
-      new String[] { LOG_ELEMENT_REFERENCES_SENT,
-                     String.valueOf(referencesSent ) },
-      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
-    };
-  }
+  /**
+   * Increments by 1 the number of entries sent to the client for this search
+   * operation.
+   */
+  public abstract void incrementEntriesSent();
 
+  /**
+   * Increments by 1 the number of search references sent to the client for this
+   * search operation.
+   */
+  public abstract void incrementReferencesSent();
 
+  /**
+   * Indicates wether the search result done message has to be sent
+   * to the client, or not.
+   *
+   * @return true if the search result done message is to be sent to the client
+   */
+  public abstract boolean isSendResponse();
+
+  /**
+   * Specify wether the search result done message has to be sent
+   * to the client, or not.
+   *
+   * @param sendResponse - boolean indicating wether the search result done
+   *                       message is to send to the client
+   */
+  public abstract void setSendResponse(boolean sendResponse);
+
+  /**
+   * Returns true if only real attributes should be returned.
+   *
+   * @return true if only real attributes should be returned, false otherwise
+   */
+  public abstract boolean isRealAttributesOnly();
+
+  /**
+   * Specify wether to only return real attributes.
+   *
+   * @param realAttributesOnly - boolean setup to true, if only the real
+   *                             attributes should be returned
+   */
+  public abstract void setRealAttributesOnly(boolean realAttributesOnly);
+
+  /**
+   * Returns true if only virtual attributes should be returned.
+   *
+   * @return true if only virtual attributes should be returned, false
+   *         otherwise
+   */
+  public abstract boolean isVirtualAttributesOnly();
+
+  /**
+   * Specify wether to only return virtual attributes.
+   *
+   * @param virtualAttributesOnly - boolean setup to true, if only the virtual
+   *                                attributes should be returned
+   */
+  public abstract void setVirtualAttributesOnly(boolean virtualAttributesOnly);
+
+  /**
+   * Sends the provided search result entry to the client.
+   *
+   * @param  entry      The search result entry to be sent to
+   *                          the client.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to send the entry to the client and
+   *                              the search should be terminated.
+   */
+  public abstract void sendSearchEntry(SearchResultEntry entry)
+    throws DirectoryException;
+
+  /**
+   * Sends the provided search result reference to the client.
+   *
+   * @param  reference  The search result reference to be sent
+   *                          to the client.
+   *
+   * @return  <CODE>true</CODE> if the client is able to accept
+   *          referrals, or <CODE>false</CODE> if the client cannot
+   *          handle referrals and no more attempts should be made to
+   *          send them for the associated search operation.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to send the reference to the client
+   *                              and the search should be terminated.
+   */
+  public abstract boolean sendSearchReference(SearchResultReference reference)
+    throws DirectoryException;
 
   /**
    * Retrieves the proxied authorization DN for this operation if proxied
@@ -1454,938 +459,17 @@
    *          authorization has been requested, or {@code null} if proxied
    *          authorization has not been requested.
    */
-  public DN getProxiedAuthorizationDN()
-  {
-    return proxiedAuthorizationDN;
-  }
-
-
+  public abstract DN getProxiedAuthorizationDN();
 
   /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final List<Control> getResponseControls()
-  {
-    return responseControls;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void addResponseControl(Control control)
-  {
-    responseControls.add(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void removeResponseControl(Control control)
-  {
-    responseControls.remove(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void run()
-  {
-    setResultCode(ResultCode.UNDEFINED);
-    boolean sendResponse = true;
-
-
-    // Get the plugin config manager that will be used for invoking plugins.
-    PluginConfigManager pluginConfigManager =
-         DirectoryServer.getPluginConfigManager();
-    boolean skipPostOperation = false;
-
-
-    // Start the processing timer.
-    processingStartTime = System.currentTimeMillis();
-    if (timeLimit <= 0)
-    {
-      timeLimitExpiration = Long.MAX_VALUE;
-    }
-    else
-    {
-      // FIXME -- Factor in the user's effective time limit.
-      timeLimitExpiration = processingStartTime + (1000L * timeLimit);
-    }
-
-
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
-      return;
-    }
-
-
-    // Create a labeled block of code that we can break out of if a problem is
-    // detected.
-searchProcessing:
-    {
-      PreParsePluginResult preParseResult =
-           pluginConfigManager.invokePreParseSearchPlugins(this);
-      if (preParseResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the request and
-        // result and return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-
-        logSearchRequest(this);
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-      else if (preParseResult.sendResponseImmediately())
-      {
-        skipPostOperation = true;
-        logSearchRequest(this);
-        break searchProcessing;
-      }
-      else if (preParseResult.skipCoreProcessing())
-      {
-        skipPostOperation = false;
-        break searchProcessing;
-      }
-
-
-      // Log the search request message.
-      logSearchRequest(this);
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-
-
-      // Process the search base and filter to convert them from their raw forms
-      // as provided by the client to the forms required for the rest of the
-      // search processing.
-      try
-      {
-        if (baseDN == null)
-        {
-          baseDN = DN.decode(rawBaseDN);
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        setMatchedDN(de.getMatchedDN());
-        setReferralURLs(de.getReferralURLs());
-
-        break searchProcessing;
-      }
-
-      try
-      {
-        if (filter == null)
-        {
-          filter = rawFilter.toSearchFilter();
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        setMatchedDN(de.getMatchedDN());
-        setReferralURLs(de.getReferralURLs());
-
-        break searchProcessing;
-      }
-
-      // Check to see if there are any controls in the request.  If so, then
-      // see if there is any special processing required.
-      boolean       processSearch    = true;
-      List<Control> requestControls  = getRequestControls();
-      if ((requestControls != null) && (! requestControls.isEmpty()))
-      {
-        for (int i=0; i < requestControls.size(); i++)
-        {
-          Control c   = requestControls.get(i);
-          String  oid = c.getOID();
-
-          if (oid.equals(OID_LDAP_ASSERTION))
-          {
-            LDAPAssertionRequestControl assertControl;
-            if (c instanceof LDAPAssertionRequestControl)
-            {
-              assertControl = (LDAPAssertionRequestControl) c;
-            }
-            else
-            {
-              try
-              {
-                assertControl = LDAPAssertionRequestControl.decodeControl(c);
-                requestControls.set(i, assertControl);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-            try
-            {
-              // FIXME -- We need to determine whether the current user has
-              //          permission to make this determination.
-              SearchFilter assertionFilter = assertControl.getSearchFilter();
-              Entry entry;
-              try
-              {
-                entry = DirectoryServer.getEntry(baseDN);
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-
-                int msgID = MSGID_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION;
-                appendErrorMessage(getMessage(msgID, de.getErrorMessage()));
-
-                break searchProcessing;
-              }
-
-              if (entry == null)
-              {
-                setResultCode(ResultCode.NO_SUCH_OBJECT);
-
-                int msgID = MSGID_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION;
-                appendErrorMessage(getMessage(msgID));
-
-                break searchProcessing;
-              }
-
-
-              if (! assertionFilter.matchesEntry(entry))
-              {
-                setResultCode(ResultCode.ASSERTION_FAILED);
-
-                appendErrorMessage(getMessage(MSGID_SEARCH_ASSERTION_FAILED));
-
-                break searchProcessing;
-              }
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              setResultCode(ResultCode.PROTOCOL_ERROR);
-
-              int msgID = MSGID_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER;
-              appendErrorMessage(getMessage(msgID, de.getErrorMessage()));
-
-              break searchProcessing;
-            }
-          }
-          else if (oid.equals(OID_PROXIED_AUTH_V1))
-          {
-            // The requester must have the PROXIED_AUTH privilige in order to be
-            // able to use this control.
-            if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-            {
-              int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-              appendErrorMessage(getMessage(msgID));
-              setResultCode(ResultCode.AUTHORIZATION_DENIED);
-              break searchProcessing;
-            }
-
-
-            ProxiedAuthV1Control proxyControl;
-            if (c instanceof ProxiedAuthV1Control)
-            {
-              proxyControl = (ProxiedAuthV1Control) c;
-            }
-            else
-            {
-              try
-              {
-                proxyControl = ProxiedAuthV1Control.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-
-            Entry authorizationEntry;
-            try
-            {
-              authorizationEntry = proxyControl.getAuthorizationEntry();
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              setResultCode(de.getResultCode());
-              appendErrorMessage(de.getErrorMessage());
-
-              break searchProcessing;
-            }
-
-            if (AccessControlConfigManager.getInstance().
-                 getAccessControlHandler().isProxiedAuthAllowed(this,
-                                                 authorizationEntry) == false) {
-              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-              int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-              appendErrorMessage(getMessage(msgID, String.valueOf(baseDN)));
-
-              skipPostOperation = true;
-              break searchProcessing;
-            }
-            setAuthorizationEntry(authorizationEntry);
-            if (authorizationEntry == null)
-            {
-              proxiedAuthorizationDN = DN.nullDN();
-            }
-            else
-            {
-              proxiedAuthorizationDN = authorizationEntry.getDN();
-            }
-          }
-          else if (oid.equals(OID_PROXIED_AUTH_V2))
-          {
-            // The requester must have the PROXIED_AUTH privilige in order to be
-            // able to use this control.
-            if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-            {
-              int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-              appendErrorMessage(getMessage(msgID));
-              setResultCode(ResultCode.AUTHORIZATION_DENIED);
-              break searchProcessing;
-            }
-
-
-            ProxiedAuthV2Control proxyControl;
-            if (c instanceof ProxiedAuthV2Control)
-            {
-              proxyControl = (ProxiedAuthV2Control) c;
-            }
-            else
-            {
-              try
-              {
-                proxyControl = ProxiedAuthV2Control.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-
-            Entry authorizationEntry;
-            try
-            {
-              authorizationEntry = proxyControl.getAuthorizationEntry();
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              setResultCode(de.getResultCode());
-              appendErrorMessage(de.getErrorMessage());
-
-              break searchProcessing;
-            }
-
-            if (AccessControlConfigManager.getInstance()
-                .getAccessControlHandler().isProxiedAuthAllowed(this,
-                                                 authorizationEntry) == false) {
-              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-              int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-              appendErrorMessage(getMessage(msgID, String.valueOf(baseDN)));
-
-              skipPostOperation = true;
-              break searchProcessing;
-            }
-
-            setAuthorizationEntry(authorizationEntry);
-            if (authorizationEntry == null)
-            {
-              proxiedAuthorizationDN = DN.nullDN();
-            }
-            else
-            {
-              proxiedAuthorizationDN = authorizationEntry.getDN();
-            }
-          }
-          else if (oid.equals(OID_PERSISTENT_SEARCH))
-          {
-            PersistentSearchControl psearchControl;
-            if (c instanceof PersistentSearchControl)
-            {
-              psearchControl = (PersistentSearchControl) c;
-            }
-            else
-            {
-              try
-              {
-                psearchControl = PersistentSearchControl.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-            persistentSearch =
-                 new PersistentSearch(this, psearchControl.getChangeTypes(),
-                                      psearchControl.getReturnECs());
-
-            // If we're only interested in changes, then we don't actually want
-            // to process the search now.
-            if (psearchControl.getChangesOnly())
-            {
-              processSearch = false;
-            }
-          }
-          else if (oid.equals(OID_LDAP_SUBENTRIES))
-          {
-            returnLDAPSubentries = true;
-          }
-          else if (oid.equals(OID_MATCHED_VALUES))
-          {
-            if (c instanceof MatchedValuesControl)
-            {
-              matchedValuesControl = (MatchedValuesControl) c;
-            }
-            else
-            {
-              try
-              {
-                matchedValuesControl = MatchedValuesControl.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-          }
-          else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
-          {
-            includeUsableControl = true;
-          }
-          else if (oid.equals(OID_REAL_ATTRS_ONLY))
-          {
-            realAttributesOnly = true;
-          }
-          else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
-          {
-            virtualAttributesOnly = true;
-          } else if(oid.equals(OID_GET_EFFECTIVE_RIGHTS)) {
-            GetEffectiveRights effectiveRightsControl;
-            if (c instanceof GetEffectiveRights)
-            {
-              effectiveRightsControl = (GetEffectiveRights) c;
-            }
-            else
-            {
-              try
-              {
-                effectiveRightsControl = GetEffectiveRights.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-              if (!AccessControlConfigManager.getInstance()
-                   .getAccessControlHandler().
-                    isGetEffectiveRightsAllowed(this, effectiveRightsControl)) {
-                 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-                 int msgID =
-                        MSGID_SEARCH_EFFECTIVERIGHTS_INSUFFICIENT_ACCESS_RIGHTS;
-                 appendErrorMessage(getMessage(msgID, String.valueOf(baseDN)));
-
-                 skipPostOperation = true;
-                 break searchProcessing;
-               }
-          }
-
-          // NYI -- Add support for additional controls.
-          else if (c.isCritical())
-          {
-            Backend backend = DirectoryServer.getBackend(baseDN);
-            if ((backend == null) || (! backend.supportsControl(oid)))
-            {
-              setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
-
-              int msgID = MSGID_SEARCH_UNSUPPORTED_CRITICAL_CONTROL;
-              appendErrorMessage(getMessage(msgID, oid));
-
-              break searchProcessing;
-            }
-          }
-        }
-      }
-
-
-      // Check to see if the client has permission to perform the
-      // search.
-
-      // FIXME: for now assume that this will check all permission
-      // pertinent to the operation. This includes proxy authorization
-      // and any other controls specified.
-      if (AccessControlConfigManager.getInstance()
-          .getAccessControlHandler().isAllowed(this) == false) {
-        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-        int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-        appendErrorMessage(getMessage(msgID, String.valueOf(baseDN)));
-
-        skipPostOperation = true;
-        break searchProcessing;
-      }
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-
-
-      // Invoke the pre-operation search plugins.
-      PreOperationPluginResult preOpResult =
-           pluginConfigManager.invokePreOperationSearchPlugins(this);
-      if (preOpResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the request and
-        // result and return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-      else if (preOpResult.sendResponseImmediately())
-      {
-        skipPostOperation = true;
-        break searchProcessing;
-      }
-      else if (preOpResult.skipCoreProcessing())
-      {
-        skipPostOperation = false;
-        break searchProcessing;
-      }
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-
-
-      // Get the backend that should hold the search base.  If there is none,
-      // then fail.
-      Backend backend = DirectoryServer.getBackend(baseDN);
-      if (backend == null)
-      {
-        setResultCode(ResultCode.NO_SUCH_OBJECT);
-        appendErrorMessage(getMessage(MSGID_SEARCH_BASE_DOESNT_EXIST,
-                                      String.valueOf(baseDN)));
-        break searchProcessing;
-      }
-
-
-      // We'll set the result code to "success".  If a problem occurs, then it
-      // will be overwritten.
-      setResultCode(ResultCode.SUCCESS);
-
-
-      // If there's a persistent search, then register it with the server.
-      if (persistentSearch != null)
-      {
-        DirectoryServer.registerPersistentSearch(persistentSearch);
-        sendResponse = false;
-      }
-
-
-      // Process the search in the backend and all its subordinates.
-      try
-      {
-        if (processSearch)
-        {
-          searchBackend(backend);
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        setMatchedDN(de.getMatchedDN());
-        setReferralURLs(de.getReferralURLs());
-
-        if (persistentSearch != null)
-        {
-          DirectoryServer.deregisterPersistentSearch(persistentSearch);
-          sendResponse = true;
-        }
-
-        break searchProcessing;
-      }
-      catch (CancelledOperationException coe)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, coe);
-        }
-
-        CancelResult cancelResult = coe.getCancelResult();
-
-        setCancelResult(cancelResult);
-        setResultCode(cancelResult.getResultCode());
-
-        String message = coe.getMessage();
-        if ((message != null) && (message.length() > 0))
-        {
-          appendErrorMessage(message);
-        }
-
-        if (persistentSearch != null)
-        {
-          DirectoryServer.deregisterPersistentSearch(persistentSearch);
-          sendResponse = true;
-        }
-
-        skipPostOperation = true;
-        break searchProcessing;
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        setResultCode(DirectoryServer.getServerErrorResultCode());
-
-        int msgID = MSGID_SEARCH_BACKEND_EXCEPTION;
-        appendErrorMessage(getMessage(msgID, getExceptionMessage(e)));
-
-        if (persistentSearch != null)
-        {
-          DirectoryServer.deregisterPersistentSearch(persistentSearch);
-          sendResponse = true;
-        }
-
-        skipPostOperation = true;
-        break searchProcessing;
-      }
-    }
-
-
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
-      logSearchResultDone(this);
-      pluginConfigManager.invokePostResponseSearchPlugins(this);
-      return;
-    }
-
-
-    // Invoke the post-operation search plugins.
-    if (! skipPostOperation)
-    {
-      PostOperationPluginResult postOperationResult =
-           pluginConfigManager.invokePostOperationSearchPlugins(this);
-      if (postOperationResult.connectionTerminated())
-      {
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-    }
-
-
-    // Indicate that it is now too late to attempt to cancel the operation.
-    setCancelResult(CancelResult.TOO_LATE);
-
-
-    // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
-
-
-    // If everything is successful to this point and it is not a persistent
-    // search, then send the search result done message to the client.
-    // Otherwise, we'll want to make the size and time limit values unlimited
-    // to ensure that the remainder of the persistent search isn't subject to
-    // those restrictions.
-    if (sendResponse)
-    {
-      sendSearchResultDone();
-    }
-    else
-    {
-      sizeLimit = 0;
-      timeLimit = 0;
-    }
-  }
-
-
-
-  /**
-   * Processes the search in the provided backend and recursively through its
-   * subordinate backends.
+   * Set the proxied authorization DN for this operation if proxied
+   * authorization has been requested.
    *
-   * @param  backend  The backend in which to process the search.
-   *
-   * @throws  DirectoryException  If a problem occurs while processing the
-   *                              search.
-   *
-   * @throws  CancelledOperationException  If the backend noticed and reacted
-   *                                       to a request to cancel or abandon the
-   *                                       search operation.
+   * @param proxiedAuthorizationDN
+   *          The proxied authorization DN for this operation if proxied
+   *          authorization has been requested, or {@code null} if proxied
+   *          authorization has not been requested.
    */
-  private final void searchBackend(Backend backend)
-          throws DirectoryException, CancelledOperationException
-  {
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      setCancelResult(CancelResult.CANCELED);
-      processingStopTime = System.currentTimeMillis();
-      return;
-    }
+  public abstract void setProxiedAuthorizationDN(DN proxiedAuthorizationDN);
 
-
-    // Perform the search in the provided backend.
-    backend.search(this);
-
-
-    // If there are any subordinate backends, then process the search there as
-    // well.
-    Backend[] subBackends = backend.getSubordinateBackends();
-    for (Backend b : subBackends)
-    {
-      DN[] baseDNs = b.getBaseDNs();
-      for (DN dn : baseDNs)
-      {
-        if (dn.isDescendantOf(baseDN))
-        {
-          searchBackend(b);
-          break;
-        }
-      }
-    }
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelResult cancel(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-
-    if (persistentSearch != null)
-    {
-      DirectoryServer.deregisterPersistentSearch(persistentSearch);
-      persistentSearch = null;
-    }
-
-    CancelResult cancelResult = getCancelResult();
-    long stopWaitingTime = System.currentTimeMillis() + 5000;
-    while ((cancelResult == null) &&
-           (System.currentTimeMillis() < stopWaitingTime))
-    {
-      try
-      {
-        Thread.sleep(50);
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-      }
-
-      cancelResult = getCancelResult();
-    }
-
-    if (cancelResult == null)
-    {
-      // This can happen in some rare cases (e.g., if a client disconnects and
-      // there is still a lot of data to send to that client), and in this case
-      // we'll prevent the cancel thread from blocking for a long period of
-      // time.
-      cancelResult = CancelResult.CANNOT_CANCEL;
-    }
-
-    return cancelResult;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelRequest getCancelRequest()
-  {
-    return cancelRequest;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-    return true;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void toString(StringBuilder buffer)
-  {
-    buffer.append("SearchOperation(connID=");
-    buffer.append(clientConnection.getConnectionID());
-    buffer.append(", opID=");
-    buffer.append(operationID);
-    buffer.append(", baseDN=");
-    buffer.append(rawBaseDN);
-    buffer.append(", scope=");
-    buffer.append(scope.toString());
-    buffer.append(", filter=");
-    buffer.append(rawFilter.toString());
-    buffer.append(")");
-  }
-}
-
+}
\ No newline at end of file

--
Gitblit v1.10.0