From 61319d28a0a0129e3a81f4f9ca770d1b63e06233 Mon Sep 17 00:00:00 2001
From: jdemendi <jdemendi@localhost>
Date: Mon, 16 Jul 2007 08:02:35 +0000
Subject: [PATCH] This fix is the refactoring of the compare operation (issue #1886).

---
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java   |  582 ++++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java |   10 
 opends/src/server/org/opends/server/core/CompareOperationBasis.java                                 |  720 ++++++++++++
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java  |   86 +
 opends/src/server/org/opends/server/api/AccessControlHandler.java                                   |    4 
 opends/src/server/org/opends/server/core/PluginConfigManager.java                                   |   12 
 opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java                        |    2 
 opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java                          |    4 
 opends/src/server/org/opends/server/core/DefaultAccessControlHandler.java                           |    2 
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java            |    9 
 opends/src/server/org/opends/server/core/CompareOperation.java                                      | 1134 -------------------
 opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java         |    4 
 opends/src/server/org/opends/server/core/OperationWrapper.java                                      |  511 +++++++++
 opends/src/server/org/opends/server/core/CompareOperationWrapper.java                               |  158 ++
 opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java                |    8 
 opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java                        |    6 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java      |   94 +
 17 files changed, 2,168 insertions(+), 1,178 deletions(-)

diff --git a/opends/src/server/org/opends/server/api/AccessControlHandler.java b/opends/src/server/org/opends/server/api/AccessControlHandler.java
index ef0b4ad..99d8a58 100644
--- a/opends/src/server/org/opends/server/api/AccessControlHandler.java
+++ b/opends/src/server/org/opends/server/api/AccessControlHandler.java
@@ -161,8 +161,8 @@
    * @return  {@code true} if the operation should be allowed by the
    *          access control configuration, or {@code false} if not.
    */
-  public abstract boolean isAllowed(CompareOperation
-                                         compareOperation);
+  public abstract boolean isAllowed(LocalBackendCompareOperation
+      compareOperation);
 
 
 
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
index d1907c5..56125c6 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
+++ b/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
@@ -880,7 +880,7 @@
      * @param operation The compare operation to check access on.
      * @return  True if access is allowed.
      */
-   public boolean isAllowed(CompareOperation operation) {
+   public boolean isAllowed(LocalBackendCompareOperation operation) {
        AciLDAPOperationContainer operationContainer =
                new AciLDAPOperationContainer(operation, ACI_COMPARE);
        String baseName;
diff --git a/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java b/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
index 2f791e4..aea44cb 100644
--- a/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
+++ b/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
@@ -58,7 +58,9 @@
      * @param operation The compare operation to evaluate.
      * @param rights  The rights of a compare operation.
      */
-    public AciLDAPOperationContainer(CompareOperation operation, int rights) {
+    public AciLDAPOperationContainer(LocalBackendCompareOperation operation,
+        int rights)
+    {
         super(operation, rights, operation.getEntryToCompare());
     }
 
diff --git a/opends/src/server/org/opends/server/core/CompareOperation.java b/opends/src/server/org/opends/server/core/CompareOperation.java
index 1a3af75..6a302e5 100644
--- a/opends/src/server/org/opends/server/core/CompareOperation.java
+++ b/opends/src/server/org/opends/server/core/CompareOperation.java
@@ -26,188 +26,27 @@
  */
 package org.opends.server.core;
 
-
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.locks.Lock;
-
-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.controls.LDAPAssertionRequestControl;
-import org.opends.server.controls.ProxiedAuthV1Control;
-import org.opends.server.controls.ProxiedAuthV2Control;
-import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.types.AbstractOperation;
-import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
-import org.opends.server.types.AttributeValue;
 import org.opends.server.types.ByteString;
-import org.opends.server.types.CancelRequest;
-import org.opends.server.types.CancelResult;
-import org.opends.server.types.Control;
-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.LDAPException;
-import org.opends.server.types.LockManager;
-import org.opends.server.types.OperationType;
-import org.opends.server.types.Privilege;
-import org.opends.server.types.ResultCode;
-import org.opends.server.types.SearchFilter;
-import org.opends.server.types.operation.PostOperationCompareOperation;
-import org.opends.server.types.operation.PostResponseCompareOperation;
-import org.opends.server.types.operation.PreOperationCompareOperation;
-import org.opends.server.types.operation.PreParseCompareOperation;
-
-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.DebugLogger;
-import org.opends.server.loggers.debug.DebugTracer;
-import org.opends.server.types.DebugLogLevel;
-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.*;
-
+import org.opends.server.types.Operation;
 
 
 /**
- * This class defines an operation that may be used to determine whether a
+ * This interface defines an operation that may be used to determine whether a
  * specified entry in the Directory Server contains a given attribute-value
  * pair.
  */
-public class CompareOperation
-       extends AbstractOperation
-       implements PreParseCompareOperation, PreOperationCompareOperation,
-                  PostOperationCompareOperation, PostResponseCompareOperation
+public interface CompareOperation extends Operation
 {
   /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = DebugLogger.getTracer();
-
-  // The attribute type for this compare operation.
-  private AttributeType attributeType;
-
-  // The assertion value for the compare operation.
-  private ByteString assertionValue;
-
-  // The raw, unprocessed entry DN as included in the client request.
-  private ByteString rawEntryDN;
-
-  // The cancel request that has been issued for this compare operation.
-  private CancelRequest cancelRequest;
-
-  // The DN of the entry for the compare operation.
-  private DN entryDN;
-
-  // The proxied authorization target DN for this operation.
-  private DN proxiedAuthorizationDN;
-
-  // The entry to be compared.
-  private Entry entry;
-
-  // The set of response controls for this compare operation.
-  private List<Control> responseControls;
-
-  // The attribute type for the compare operation.
-  private String rawAttributeType;
-
-
-
-  /**
-   * Creates a new compare 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  rawEntryDN        The raw, unprocessed entry DN as provided in the
-   *                           client request.  This may or may not be a valid
-   *                           DN as no validation will have been performed yet.
-   * @param  rawAttributeType  The raw attribute type for the compare operation.
-   * @param  assertionValue    The assertion value for the compare operation.
-   */
-  public CompareOperation(ClientConnection clientConnection, long operationID,
-                          int messageID, List<Control> requestControls,
-                          ByteString rawEntryDN, String rawAttributeType,
-                          ByteString assertionValue)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.rawEntryDN       = rawEntryDN;
-    this.rawAttributeType = rawAttributeType;
-    this.assertionValue   = assertionValue;
-
-    responseControls       = new ArrayList<Control>();
-    entry                  = null;
-    entryDN                = null;
-    attributeType          = null;
-    cancelRequest          = null;
-    proxiedAuthorizationDN = null;
-  }
-
-
-
-  /**
-   * Creates a new compare 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  entryDN           The entry DN for this compare operation.
-   * @param  attributeType     The attribute type for this compare operation.
-   * @param  assertionValue    The assertion value for the compare operation.
-   */
-  public CompareOperation(ClientConnection clientConnection, long operationID,
-                          int messageID, List<Control> requestControls,
-                          DN entryDN, AttributeType attributeType,
-                          ByteString assertionValue)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.entryDN        = entryDN;
-    this.attributeType  = attributeType;
-    this.assertionValue = assertionValue;
-
-    responseControls       = new ArrayList<Control>();
-    rawEntryDN             = new ASN1OctetString(entryDN.toString());
-    rawAttributeType       = attributeType.getNameOrOID();
-    cancelRequest          = null;
-    entry                  = null;
-    proxiedAuthorizationDN = null;
-  }
-
-
-
-  /**
    * Retrieves the raw, unprocessed entry DN as included in the client request.
    * The DN that is returned may or may not be a valid DN, since no validation
    * will have been performed upon it.
    *
    * @return  The raw, unprocessed entry DN as included in the client request.
    */
-  public final ByteString getRawEntryDN()
-  {
-    return rawEntryDN;
-  }
-
+  public ByteString getRawEntryDN();
 
 
   /**
@@ -217,13 +56,7 @@
    * @param  rawEntryDN  The raw, unprocessed entry DN as included in the client
    *                     request.
    */
-  public final void setRawEntryDN(ByteString rawEntryDN)
-  {
-    this.rawEntryDN = rawEntryDN;
-
-    entryDN = null;
-  }
-
+  public void setRawEntryDN(ByteString rawEntryDN);
 
 
   /**
@@ -234,11 +67,7 @@
    * @return  The DN of the entry to compare, or <CODE>null</CODE> if the raw
    *          entry DN has not yet been processed.
    */
-  public final DN getEntryDN()
-  {
-    return entryDN;
-  }
-
+  public DN getEntryDN();
 
 
   /**
@@ -246,11 +75,7 @@
    *
    * @return  The raw attribute type for this compare operation.
    */
-  public final String getRawAttributeType()
-  {
-    return rawAttributeType;
-  }
-
+  public String getRawAttributeType();
 
 
   /**
@@ -260,13 +85,7 @@
    * @param  rawAttributeType  The raw attribute type for this compare
    *                           operation.
    */
-  public final void setRawAttributeType(String rawAttributeType)
-  {
-    this.rawAttributeType = rawAttributeType;
-
-    attributeType = null;
-  }
-
+  public void setRawAttributeType(String rawAttributeType);
 
 
   /**
@@ -276,23 +95,23 @@
    *
    * @return  The attribute type for this compare operation.
    */
-  public final AttributeType getAttributeType()
-  {
-    return attributeType;
-  }
+  public AttributeType getAttributeType();
 
 
+  /**
+   * Specifies the attribute type for this compare operation.
+   *
+   * @param attributeType  The attribute type for this compare operation.
+   */
+  public void setAttributeType(AttributeType attributeType);
+
 
   /**
    * Retrieves the assertion value for this compare operation.
    *
    * @return  The assertion value for this compare operation.
    */
-  public final ByteString getAssertionValue()
-  {
-    return assertionValue;
-  }
-
+  public ByteString getAssertionValue();
 
 
   /**
@@ -301,926 +120,23 @@
    *
    * @param  assertionValue  The assertion value for this compare operation.
    */
-  public final void setAssertionValue(ByteString assertionValue)
-  {
-    this.assertionValue = assertionValue;
-  }
-
+  public void setAssertionValue(ByteString assertionValue);
 
 
   /**
-   * Retrieves the entry to target with the compare operation.  This should not
-   * be called by pre-parse plugins.
+   * Retrieves the proxied authorization target DN for this compare operation.
    *
-   * @return  The entry to target with the compare operation, or
-   *          <CODE>null</CODE> if the entry is not yet available.
+   * @return  The proxied authorization target DN for this compare operation
    */
-  public final Entry getEntryToCompare()
-  {
-    return entry;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @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.COMPARE;
-  }
-
+  public DN getProxiedAuthorizationDN();
 
 
   /**
-   * {@inheritDoc}
-   */
-  @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);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @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.
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) },
-      new String[] { LOG_ELEMENT_COMPARE_ATTR, rawAttributeType }
-    };
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @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.
-
-    String resultCode = String.valueOf(getResultCode().getIntValue());
-
-    String errorMessage;
-    StringBuilder errorMessageBuffer = getErrorMessage();
-    if (errorMessageBuffer == null)
-    {
-      errorMessage = null;
-    }
-    else
-    {
-      errorMessage = errorMessageBuffer.toString();
-    }
-
-    String matchedDNStr;
-    DN matchedDN = getMatchedDN();
-    if (matchedDN == null)
-    {
-      matchedDNStr = null;
-    }
-    else
-    {
-      matchedDNStr = matchedDN.toString();
-    }
-
-    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());
-
-      while (iterator.hasNext())
-      {
-        buffer.append(", ");
-        buffer.append(iterator.next());
-      }
-
-      referrals = buffer.toString();
-    }
-
-    String processingTime =
-         String.valueOf(getProcessingTime());
-
-    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_PROCESSING_TIME, processingTime }
-    };
-  }
-
-
-
-  /**
-   * Retrieves the proxied authorization DN for this operation if proxied
-   * authorization has been requested.
+   * Specifies the proxied authorization target DN for this compare operation.
    *
-   * @return  The proxied authorization DN for this operation if proxied
-   *          authorization has been requested, or {@code null} if proxied
-   *          authorization has not been requested.
+   * @param proxiedAuthorizationDN  The proxied authorization target DN for
+   *                                this compare operation
    */
-  public DN getProxiedAuthorizationDN()
-  {
-    return proxiedAuthorizationDN;
-  }
-
-
-
-  /**
-   * {@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);
-  }
-
-
-
-  /**
-   * Performs the work of actually processing this operation.  This
-   * should include all processing for the operation, including
-   * invoking plugins, logging messages, performing access control,
-   * managing synchronization, and any other work that might need to
-   * be done in the course of processing.
-   */
-  public final void run()
-  {
-    setResultCode(ResultCode.UNDEFINED);
-
-
-    // Get the plugin config manager that will be used for invoking plugins.
-    PluginConfigManager pluginConfigManager =
-         DirectoryServer.getPluginConfigManager();
-    boolean skipPostOperation = false;
-
-
-    // Start the processing timer.
-    setProcessingStartTime();
-
-
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      indicateCancelled(cancelRequest);
-      setProcessingStopTime();
-      return;
-    }
-
-
-    // Create a labeled block of code that we can break out of if a problem is
-    // detected.
-compareProcessing:
-    {
-      // Invoke the pre-parse compare plugins.
-      PreParsePluginResult preParseResult =
-           pluginConfigManager.invokePreParseComparePlugins(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));
-
-        setProcessingStopTime();
-
-        logCompareRequest(this);
-        logCompareResponse(this);
-        pluginConfigManager.invokePostResponseComparePlugins(this);
-        return;
-      }
-      else if (preParseResult.sendResponseImmediately())
-      {
-        skipPostOperation = true;
-        logCompareRequest(this);
-        break compareProcessing;
-      }
-      else if (preParseResult.skipCoreProcessing())
-      {
-        skipPostOperation = false;
-        break compareProcessing;
-      }
-
-
-      // Log the compare request message.
-      logCompareRequest(this);
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        setProcessingStopTime();
-        logCompareResponse(this);
-        pluginConfigManager.invokePostResponseComparePlugins(this);
-        return;
-      }
-
-
-      // Process the entry DN to convert it from the raw form to the form
-      // required for the rest of the compare processing.
-      try
-      {
-        if (entryDN == null)
-        {
-          entryDN = DN.decode(rawEntryDN);
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        skipPostOperation = true;
-
-        break compareProcessing;
-      }
-
-
-      // If the target entry is in the server configuration, then make sure the
-      // requester has the CONFIG_READ privilege.
-      if (DirectoryServer.getConfigHandler().handlesEntry(entryDN) &&
-          (! clientConnection.hasPrivilege(Privilege.CONFIG_READ, this)))
-      {
-        int msgID = MSGID_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES;
-        appendErrorMessage(getMessage(msgID));
-        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-        skipPostOperation = true;
-
-        break compareProcessing;
-      }
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        setProcessingStopTime();
-        logCompareResponse(this);
-        pluginConfigManager.invokePostResponseComparePlugins(this);
-        return;
-      }
-
-
-      // Grab a read lock on the entry.
-      Lock readLock = null;
-      for (int i=0; i < 3; i++)
-      {
-        readLock = LockManager.lockRead(entryDN);
-        if (readLock != null)
-        {
-          break;
-        }
-      }
-
-      if (readLock == null)
-      {
-        int    msgID   = MSGID_COMPARE_CANNOT_LOCK_ENTRY;
-        String message = getMessage(msgID, String.valueOf(entryDN));
-
-        setResultCode(DirectoryServer.getServerErrorResultCode());
-        appendErrorMessage(message);
-        skipPostOperation = true;
-        break compareProcessing;
-      }
-
-      try
-      {
-        // Get the entry.  If it does not exist, then fail.
-        try
-        {
-          entry = DirectoryServer.getEntry(entryDN);
-
-          if (entry == null)
-          {
-            setResultCode(ResultCode.NO_SUCH_OBJECT);
-            appendErrorMessage(getMessage(MSGID_COMPARE_NO_SUCH_ENTRY,
-                                          String.valueOf(entryDN)));
-
-            // See if one of the entry's ancestors exists.
-            DN parentDN = entryDN.getParentDNInSuffix();
-            while (parentDN != null)
-            {
-              try
-              {
-                if (DirectoryServer.entryExists(parentDN))
-                {
-                  setMatchedDN(parentDN);
-                  break;
-                }
-              }
-              catch (Exception e)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                }
-                break;
-              }
-
-              parentDN = parentDN.getParentDNInSuffix();
-            }
-
-            break compareProcessing;
-          }
-        }
-        catch (DirectoryException de)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
-          }
-
-          setResultCode(de.getResultCode());
-          appendErrorMessage(de.getErrorMessage());
-          break compareProcessing;
-        }
-
-        // Check to see if there are any controls in the request.  If so, then
-        // see if there is any special processing required.
-        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 compareProcessing;
-                }
-              }
-
-              try
-              {
-                // FIXME -- We need to determine whether the current user has
-                //          permission to make this determination.
-                SearchFilter filter = assertControl.getSearchFilter();
-                if (! filter.matchesEntry(entry))
-                {
-                  setResultCode(ResultCode.ASSERTION_FAILED);
-
-                  appendErrorMessage(getMessage(MSGID_COMPARE_ASSERTION_FAILED,
-                                                String.valueOf(entryDN)));
-
-                  break compareProcessing;
-                }
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(ResultCode.PROTOCOL_ERROR);
-
-                int msgID = MSGID_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              de.getErrorMessage()));
-
-                break compareProcessing;
-              }
-            }
-            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 compareProcessing;
-              }
-
-
-              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 compareProcessing;
-                }
-              }
-
-
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
-
-                break compareProcessing;
-              }
-
-              if (AccessControlConfigManager.getInstance()
-                      .getAccessControlHandler().isProxiedAuthAllowed(this,
-                                                 authorizationEntry) == false) {
-                setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-                int msgID = MSGID_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-                skipPostOperation = true;
-                break compareProcessing;
-              }
-              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 compareProcessing;
-              }
-
-
-              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 compareProcessing;
-                }
-              }
-
-
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
-
-                break compareProcessing;
-              }
-
-              if (AccessControlConfigManager.getInstance()
-                      .getAccessControlHandler().isProxiedAuthAllowed(this,
-                                                 authorizationEntry) == false) {
-                setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-                int msgID = MSGID_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-                skipPostOperation = true;
-                break compareProcessing;
-              }
-              setAuthorizationEntry(authorizationEntry);
-              if (authorizationEntry == null)
-              {
-                proxiedAuthorizationDN = DN.nullDN();
-              }
-              else
-              {
-                proxiedAuthorizationDN = authorizationEntry.getDN();
-              }
-            }
-
-            // NYI -- Add support for additional controls.
-            else if (c.isCritical())
-            {
-              Backend backend = DirectoryServer.getBackend(entryDN);
-              if ((backend == null) || (! backend.supportsControl(oid)))
-              {
-                setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
-
-                int msgID = MSGID_COMPARE_UNSUPPORTED_CRITICAL_CONTROL;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              oid));
-
-                break compareProcessing;
-              }
-            }
-          }
-        }
-
-
-        // Check to see if the client has permission to perform the
-        // compare.
-
-        // FIXME: for now assume that this will check all permission
-        // pertinent to the operation. This includes proxy authorization
-        // and any other controls specified.
-
-        // FIXME: earlier checks to see if the entry already exists may
-        // have already exposed sensitive information to the client.
-        if (AccessControlConfigManager.getInstance()
-            .getAccessControlHandler().isAllowed(this) == false) {
-          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-          int msgID = MSGID_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-          appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-          skipPostOperation = true;
-          break compareProcessing;
-        }
-
-        // Check for and handle a request to cancel this operation.
-        if (cancelRequest != null)
-        {
-          indicateCancelled(cancelRequest);
-          setProcessingStopTime();
-          logCompareResponse(this);
-          pluginConfigManager.invokePostResponseComparePlugins(this);
-          return;
-        }
-
-
-        // Invoke the pre-operation compare plugins.
-        PreOperationPluginResult preOpResult =
-             pluginConfigManager.invokePreOperationComparePlugins(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));
-
-          setProcessingStopTime();
-          logCompareResponse(this);
-          pluginConfigManager.invokePostResponseComparePlugins(this);
-          return;
-        }
-        else if (preOpResult.sendResponseImmediately())
-        {
-          skipPostOperation = true;
-          break compareProcessing;
-        }
-        else if (preOpResult.skipCoreProcessing())
-        {
-          skipPostOperation = false;
-          break compareProcessing;
-        }
-
-
-        // Get the base attribute type and set of options.
-        String          baseName;
-        HashSet<String> options;
-        int             semicolonPos = rawAttributeType.indexOf(';');
-        if (semicolonPos > 0)
-        {
-          baseName = toLowerCase(rawAttributeType.substring(0, semicolonPos));
-
-          options = new HashSet<String>();
-          int nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
-          while (nextPos > 0)
-          {
-            options.add(rawAttributeType.substring(semicolonPos+1, nextPos));
-            semicolonPos = nextPos;
-            nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
-          }
-
-          options.add(rawAttributeType.substring(semicolonPos+1));
-        }
-        else
-        {
-          baseName = toLowerCase(rawAttributeType);
-          options  = null;
-        }
-
-
-        // Actually perform the compare operation.
-        List<Attribute> attrList = null;
-        if (attributeType == null)
-        {
-          attributeType = DirectoryServer.getAttributeType(baseName);
-        }
-        if (attributeType == null)
-        {
-          attrList = entry.getAttribute(baseName, options);
-          attributeType = DirectoryServer.getDefaultAttributeType(baseName);
-        }
-        else
-        {
-          attrList = entry.getAttribute(attributeType, options);
-        }
-
-        if ((attrList == null) || attrList.isEmpty())
-        {
-          setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
-          if (options == null)
-          {
-            appendErrorMessage(getMessage(MSGID_COMPARE_OP_NO_SUCH_ATTR,
-                                          String.valueOf(entryDN), baseName));
-          }
-          else
-          {
-            appendErrorMessage(getMessage(
-                                    MSGID_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS,
-                                    String.valueOf(entryDN), baseName));
-          }
-        }
-        else
-        {
-          AttributeValue value = new AttributeValue(attributeType,
-                                                    assertionValue);
-
-          boolean matchFound = false;
-          for (Attribute a : attrList)
-          {
-            if (a.hasValue(value))
-            {
-              matchFound = true;
-              break;
-            }
-          }
-
-          if (matchFound)
-          {
-            setResultCode(ResultCode.COMPARE_TRUE);
-          }
-          else
-          {
-            setResultCode(ResultCode.COMPARE_FALSE);
-          }
-        }
-      }
-      finally
-      {
-        LockManager.unlock(entryDN, readLock);
-      }
-    }
-
-
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      indicateCancelled(cancelRequest);
-      setProcessingStopTime();
-      logCompareResponse(this);
-      pluginConfigManager.invokePostResponseComparePlugins(this);
-      return;
-    }
-
-
-    // Invoke the post-operation compare plugins.
-    if (! skipPostOperation)
-    {
-      PostOperationPluginResult postOperationResult =
-           pluginConfigManager.invokePostOperationComparePlugins(this);
-      if (postOperationResult.connectionTerminated())
-      {
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        setProcessingStopTime();
-        logCompareResponse(this);
-        pluginConfigManager.invokePostResponseComparePlugins(this);
-        return;
-      }
-    }
-
-
-    // Indicate that it is now too late to attempt to cancel the operation.
-    setCancelResult(CancelResult.TOO_LATE);
-
-
-    // Stop the processing timer.
-    setProcessingStopTime();
-
-
-    // Send the compare response to the client.
-    clientConnection.sendResponse(this);
-
-
-    // Log the compare response message.
-    logCompareResponse(this);
-
-
-    // Invoke the post-response compare plugins.
-    pluginConfigManager.invokePostResponseComparePlugins(this);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelResult cancel(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-
-    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()
-  public boolean setCancelRequest(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-    return true;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void toString(StringBuilder buffer)
-  {
-    buffer.append("CompareOperation(connID=");
-    buffer.append(clientConnection.getConnectionID());
-    buffer.append(", opID=");
-    buffer.append(operationID);
-    buffer.append(", dn=");
-    buffer.append(rawEntryDN);
-    buffer.append(", attr=");
-    buffer.append(rawAttributeType);
-    buffer.append(")");
-  }
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN);
 
 }
-
diff --git a/opends/src/server/org/opends/server/core/CompareOperationBasis.java b/opends/src/server/org/opends/server/core/CompareOperationBasis.java
new file mode 100644
index 0000000..e4f74f8
--- /dev/null
+++ b/opends/src/server/org/opends/server/core/CompareOperationBasis.java
@@ -0,0 +1,720 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+import static org.opends.server.core.CoreConstants.*;
+import static org.opends.server.loggers.AccessLogger.logCompareRequest;
+import static org.opends.server.loggers.AccessLogger.logCompareResponse;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.messages.CoreMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.plugin.PreParsePluginResult;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.AbstractOperation;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+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.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Operation;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.operation.PostResponseCompareOperation;
+import org.opends.server.types.operation.PreParseCompareOperation;
+import org.opends.server.workflowelement.localbackend.
+       LocalBackendCompareOperation;
+
+
+/**
+ * This class defines an operation that may be used to determine whether a
+ * specified entry in the Directory Server contains a given attribute-value
+ * pair.
+ */
+public class CompareOperationBasis
+             extends AbstractOperation
+             implements PreParseCompareOperation, CompareOperation,
+                        Runnable, PostResponseCompareOperation
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
+
+  // The attribute type for this compare operation.
+  private AttributeType attributeType;
+
+  // The assertion value for the compare operation.
+  private ByteString assertionValue;
+
+  // The raw, unprocessed entry DN as included in the client request.
+  private ByteString rawEntryDN;
+
+  // The cancel request that has been issued for this compare operation.
+  private CancelRequest cancelRequest;
+
+  // The DN of the entry for the compare operation.
+  private DN entryDN;
+
+  // The proxied authorization target DN for this operation.
+  private DN proxiedAuthorizationDN;
+
+  // The set of response controls for this compare operation.
+  private List<Control> responseControls;
+
+  // The attribute type for the compare operation.
+  private String rawAttributeType;
+
+
+
+  /**
+   * Creates a new compare 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  rawEntryDN        The raw, unprocessed entry DN as provided in the
+   *                           client request.  This may or may not be a valid
+   *                           DN as no validation will have been performed yet.
+   * @param  rawAttributeType  The raw attribute type for the compare operation.
+   * @param  assertionValue    The assertion value for the compare operation.
+   */
+  public CompareOperationBasis(
+                          ClientConnection clientConnection, long operationID,
+                          int messageID, List<Control> requestControls,
+                          ByteString rawEntryDN, String rawAttributeType,
+                          ByteString assertionValue)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.rawEntryDN       = rawEntryDN;
+    this.rawAttributeType = rawAttributeType;
+    this.assertionValue   = assertionValue;
+
+    responseControls       = new ArrayList<Control>();
+    entryDN                = null;
+    attributeType          = null;
+    cancelRequest          = null;
+    proxiedAuthorizationDN = null;
+  }
+
+
+
+  /**
+   * Creates a new compare 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  entryDN           The entry DN for this compare operation.
+   * @param  attributeType     The attribute type for this compare operation.
+   * @param  assertionValue    The assertion value for the compare operation.
+   */
+  public CompareOperationBasis(
+                          ClientConnection clientConnection, long operationID,
+                          int messageID, List<Control> requestControls,
+                          DN entryDN, AttributeType attributeType,
+                          ByteString assertionValue)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.entryDN        = entryDN;
+    this.attributeType  = attributeType;
+    this.assertionValue = assertionValue;
+
+    responseControls       = new ArrayList<Control>();
+    rawEntryDN             = new ASN1OctetString(entryDN.toString());
+    rawAttributeType       = attributeType.getNameOrOID();
+    cancelRequest          = null;
+    proxiedAuthorizationDN = null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ByteString getRawEntryDN()
+  {
+    return rawEntryDN;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawEntryDN(ByteString rawEntryDN)
+  {
+    this.rawEntryDN = rawEntryDN;
+
+    entryDN = null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DN getEntryDN()
+  {
+    return entryDN;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String getRawAttributeType()
+  {
+    return rawAttributeType;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawAttributeType(String rawAttributeType)
+  {
+    this.rawAttributeType = rawAttributeType;
+
+    attributeType = null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final AttributeType getAttributeType()
+  {
+    return attributeType;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttributeType(AttributeType attributeType)
+  {
+    this.attributeType = attributeType;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ByteString getAssertionValue()
+  {
+    return assertionValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setAssertionValue(ByteString assertionValue)
+  {
+    this.assertionValue = assertionValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @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.COMPARE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @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);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @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.
+
+    return new String[][]
+    {
+      new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) },
+      new String[] { LOG_ELEMENT_COMPARE_ATTR, rawAttributeType }
+    };
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @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.
+
+    String resultCode = String.valueOf(getResultCode().getIntValue());
+
+    String errorMessage;
+    StringBuilder errorMessageBuffer = getErrorMessage();
+    if (errorMessageBuffer == null)
+    {
+      errorMessage = null;
+    }
+    else
+    {
+      errorMessage = errorMessageBuffer.toString();
+    }
+
+    String matchedDNStr;
+    DN matchedDN = getMatchedDN();
+    if (matchedDN == null)
+    {
+      matchedDNStr = null;
+    }
+    else
+    {
+      matchedDNStr = matchedDN.toString();
+    }
+
+    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());
+
+      while (iterator.hasNext())
+      {
+        buffer.append(", ");
+        buffer.append(iterator.next());
+      }
+
+      referrals = buffer.toString();
+    }
+
+    String processingTime =
+         String.valueOf(getProcessingTime());
+
+    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_PROCESSING_TIME, processingTime }
+    };
+  }
+
+
+
+  /**
+   * Retrieves the proxied authorization DN for this operation if proxied
+   * authorization has been requested.
+   *
+   * @return  The proxied authorization DN for this operation if proxied
+   *          authorization has been requested, or {@code null} if proxied
+   *          authorization has not been requested.
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return proxiedAuthorizationDN;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
+  {
+    this.proxiedAuthorizationDN = proxiedAuthorizationDN;
+  }
+
+
+
+  /**
+   * {@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);
+  }
+
+
+
+  /**
+   * Performs the work of actually processing this operation.  This
+   * should include all processing for the operation, including
+   * invoking plugins, logging messages, performing access control,
+   * managing synchronization, and any other work that might need to
+   * be done in the course of processing.
+   */
+  public final void run()
+  {
+    setResultCode(ResultCode.UNDEFINED);
+
+    // Start the processing timer.
+    setProcessingStartTime();
+
+    // Check for and handle a request to cancel this operation.
+    if (cancelRequest != null)
+    {
+      indicateCancelled(cancelRequest);
+      setProcessingStopTime();
+      return;
+    }
+
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+         DirectoryServer.getPluginConfigManager();
+
+
+    // Create a labeled block of code that we can break out of if a problem is
+    // detected.
+    boolean workflowExecuted = false;
+compareProcessing:
+    {
+      // Invoke the pre-parse compare plugins.
+      PreParsePluginResult preParseResult =
+           pluginConfigManager.invokePreParseComparePlugins(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));
+
+        setProcessingStopTime();
+
+        logCompareRequest(this);
+        logCompareResponse(this);
+        pluginConfigManager.invokePostResponseComparePlugins(this);
+        return;
+      }
+      else if (preParseResult.sendResponseImmediately())
+      {
+        logCompareRequest(this);
+        break compareProcessing;
+      }
+      else if (preParseResult.skipCoreProcessing())
+      {
+        break compareProcessing;
+      }
+
+
+      // Log the compare request message.
+      logCompareRequest(this);
+
+
+      // Check for and handle a request to cancel this operation.
+      if (cancelRequest != null)
+      {
+        indicateCancelled(cancelRequest);
+        setProcessingStopTime();
+        logCompareResponse(this);
+        pluginConfigManager.invokePostResponseComparePlugins(this);
+        return;
+      }
+
+
+      // Process the entry DN to convert it from the raw form to the form
+      // required for the rest of the compare processing.
+      try
+      {
+        if (entryDN == null)
+        {
+          entryDN = DN.decode(rawEntryDN);
+        }
+      }
+      catch (DirectoryException de)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+
+        setResultCode(de.getResultCode());
+        appendErrorMessage(de.getErrorMessage());
+
+        break compareProcessing;
+      }
+
+
+      // Retrieve the network group registered with the client connection
+      // and get a workflow to process the operation.
+      NetworkGroup ng = getClientConnection().getNetworkGroup();
+      Workflow workflow = ng.getWorkflowCandidate(entryDN);
+      if (workflow == null)
+      {
+        // We have found no workflow for the requested base DN, just return
+        // a no such entry result code and stop the processing.
+        updateOperationErrMsgAndResCode();
+        break compareProcessing;
+      }
+      workflow.execute(this);
+      workflowExecuted = true;
+    }
+
+    // Check for and handle a request to cancel this operation.
+    if (cancelRequest != null)
+    {
+      indicateCancelled(cancelRequest);
+      setProcessingStopTime();
+      logCompareResponse(this);
+      pluginConfigManager.invokePostResponseComparePlugins(this);
+      return;
+    }
+
+    // Indicate that it is now too late to attempt to cancel the operation.
+    setCancelResult(CancelResult.TOO_LATE);
+
+    // Stop the processing timer.
+    setProcessingStopTime();
+
+    // Send the compare response to the client unless the operation result
+    // code is CANCELED
+    if (getResultCode() != ResultCode.CANCELED)
+    {
+      clientConnection.sendResponse(this);
+    }
+
+    // Log the compare response message.
+    logCompareResponse(this);
+
+    // Invoke the post-response compare plugins.
+    if (workflowExecuted)
+    {
+      List localOperations =
+        (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
+      if (localOperations != null)
+      {
+        for (Object localOp : localOperations)
+        {
+          LocalBackendCompareOperation localOperation =
+            (LocalBackendCompareOperation)localOp;
+          pluginConfigManager.invokePostResponseComparePlugins(localOperation);
+        }
+      }
+    }
+    else
+    {
+      pluginConfigManager.invokePostResponseComparePlugins(this);
+    }
+  }
+
+
+  /**
+   * Updates the error message and the result code of the operation.
+   *
+   * This method is called because no workflow was found to process
+   * the operation.
+   */
+  private void updateOperationErrMsgAndResCode()
+  {
+    setResultCode(ResultCode.NO_SUCH_OBJECT);
+    appendErrorMessage(
+      getMessage(MSGID_COMPARE_NO_SUCH_ENTRY, String.valueOf(getEntryDN())));
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final CancelResult cancel(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+
+    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()
+  public boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void toString(StringBuilder buffer)
+  {
+    buffer.append("CompareOperation(connID=");
+    buffer.append(clientConnection.getConnectionID());
+    buffer.append(", opID=");
+    buffer.append(operationID);
+    buffer.append(", dn=");
+    buffer.append(rawEntryDN);
+    buffer.append(", attr=");
+    buffer.append(rawAttributeType);
+    buffer.append(")");
+  }
+
+
+  /**
+   * {@inheritDoc}
+   *
+   * This method always returns null.
+   */
+  public Entry getEntryToCompare()
+  {
+    return null;
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/core/CompareOperationWrapper.java b/opends/src/server/org/opends/server/core/CompareOperationWrapper.java
new file mode 100644
index 0000000..c19ba40
--- /dev/null
+++ b/opends/src/server/org/opends/server/core/CompareOperationWrapper.java
@@ -0,0 +1,158 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.DN;
+
+
+/**
+ * This abstract class wraps/decorates a given compare operation.
+ * This class will be extended by sub-classes to enhance the
+ * functionnality of the CompareOperationBasis.
+ */
+public abstract class CompareOperationWrapper
+  extends OperationWrapper
+  implements CompareOperation
+{
+  // The wrapped operation
+  private CompareOperation compare;
+
+
+  /**
+   * Creates a new compare operation based on the provided compare operation.
+   *
+   * @param compare The compare operation to wrap
+   */
+  public CompareOperationWrapper(CompareOperation compare)
+  {
+    super(compare);
+    this.compare = compare;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getRawEntryDN()
+  {
+    return compare.getRawEntryDN();
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawEntryDN(ByteString rawEntryDN)
+  {
+    compare.setRawEntryDN(rawEntryDN);
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getEntryDN()
+  {
+    return compare.getEntryDN();
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getRawAttributeType()
+  {
+    return compare.getRawAttributeType();
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawAttributeType(String rawAttributeType)
+  {
+    compare.setRawAttributeType(rawAttributeType);
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AttributeType getAttributeType()
+  {
+    return compare.getAttributeType();
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttributeType(AttributeType attributeType)
+  {
+    compare.setAttributeType(attributeType);
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getAssertionValue()
+  {
+    return compare.getAssertionValue();
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAssertionValue(ByteString assertionValue)
+  {
+    compare.setAssertionValue(assertionValue);
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return compare.getProxiedAuthorizationDN();
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
+  {
+    compare.setProxiedAuthorizationDN(proxiedAuthorizationDN);
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/core/DefaultAccessControlHandler.java b/opends/src/server/org/opends/server/core/DefaultAccessControlHandler.java
index 2a4a8ed..50e2393 100644
--- a/opends/src/server/org/opends/server/core/DefaultAccessControlHandler.java
+++ b/opends/src/server/org/opends/server/core/DefaultAccessControlHandler.java
@@ -116,7 +116,7 @@
    * {@inheritDoc}
    */
   @Override
-  public boolean isAllowed(CompareOperation compareOperation)
+  public boolean isAllowed(LocalBackendCompareOperation compareOperation)
   {
     return true;
   }
diff --git a/opends/src/server/org/opends/server/core/OperationWrapper.java b/opends/src/server/org/opends/server/core/OperationWrapper.java
new file mode 100644
index 0000000..af9b4bf
--- /dev/null
+++ b/opends/src/server/org/opends/server/core/OperationWrapper.java
@@ -0,0 +1,511 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Operation;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.ResultCode;
+
+
+/**
+ * This abstract class is a generic operation wrapper intended to be
+ * subclassed by a specific operation wrapper.
+ */
+public class OperationWrapper implements Operation
+{
+  // The wrapped operation.
+  private Operation operation;
+
+
+  /**
+   * Creates a new generic operation wrapper.
+   *
+   * @param operation  the generic operation to wrap
+   */
+  public OperationWrapper(Operation operation)
+  {
+    this.operation = operation;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addRequestControl(Control control)
+  {
+    operation.addRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addResponseControl(Control control)
+  {
+    operation.addResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendAdditionalLogMessage(String message)
+  {
+    operation.appendAdditionalLogMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendErrorMessage(String message)
+  {
+    operation.appendErrorMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult cancel(CancelRequest cancelRequest)
+  {
+    return operation.cancel(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void disconnectClient(
+    DisconnectReason disconnectReason,
+    boolean sendNotification,
+    String message,
+    int messageID)
+  {
+    operation.disconnectClient(
+      disconnectReason, sendNotification, message, messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean dontSynchronize()
+  {
+    return operation.dontSynchronize();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getAdditionalLogMessage()
+  {
+    return operation.getAdditionalLogMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object getAttachment(String name)
+  {
+    return operation.getAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<String, Object> getAttachments()
+  {
+    return operation.getAttachments();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getAuthorizationDN()
+  {
+    return operation.getAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry getAuthorizationEntry()
+  {
+    return operation.getAuthorizationEntry();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelRequest getCancelRequest()
+  {
+    return operation.getCancelRequest();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult getCancelResult()
+  {
+    return operation.getCancelResult();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ClientConnection getClientConnection()
+  {
+    return operation.getClientConnection();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getCommonLogElements()
+  {
+    return operation.getCommonLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getConnectionID()
+  {
+    return operation.getConnectionID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getErrorMessage()
+  {
+    return operation.getErrorMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getMatchedDN()
+  {
+    return operation.getMatchedDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getMessageID()
+  {
+    return operation.getMessageID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getOperationID()
+  {
+    return operation.getOperationID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public OperationType getOperationType()
+  {
+    return operation.getOperationType();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStartTime()
+  {
+    return operation.getProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStopTime()
+  {
+    return operation.getProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingTime()
+  {
+    return operation.getProcessingTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<String> getReferralURLs()
+  {
+    return operation.getReferralURLs();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getRequestControls()
+  {
+    return operation.getRequestControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getRequestLogElements()
+  {
+    return operation.getRequestLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getResponseControls()
+  {
+    return operation.getResponseControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getResponseLogElements()
+  {
+    return operation.getResponseLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ResultCode getResultCode()
+  {
+    return operation.getResultCode();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void indicateCancelled(CancelRequest cancelRequest)
+  {
+    operation.indicateCancelled(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isInternalOperation()
+  {
+    return operation.isInternalOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isSynchronizationOperation()
+  {
+    return operation.isSynchronizationOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void operationCompleted()
+  {
+    operation.operationCompleted();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object removeAttachment(String name)
+  {
+    return operation.removeAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeRequestControl(Control control)
+  {
+    operation.removeRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeResponseControl(Control control)
+  {
+    operation.removeResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAdditionalLogMessage(StringBuilder additionalLogMessage)
+  {
+    operation.setAdditionalLogMessage(additionalLogMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object setAttachment(String name, Object value)
+  {
+    return operation.setAttachment(name, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttachments(Map<String, Object> attachments)
+  {
+    operation.setAttachments(attachments);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAuthorizationEntry(Entry authorizationEntry)
+  {
+    operation.setAuthorizationEntry(authorizationEntry);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    return operation.setCancelRequest(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setCancelResult(CancelResult cancelResult)
+  {
+    operation.setCancelResult(cancelResult);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setDontSynchronize(boolean dontSynchronize)
+  {
+    operation.setDontSynchronize(dontSynchronize);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setErrorMessage(StringBuilder errorMessage)
+  {
+    operation.setErrorMessage(errorMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setInternalOperation(boolean isInternalOperation)
+  {
+    operation.setInternalOperation(isInternalOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setMatchedDN(DN matchedDN)
+  {
+    operation.setMatchedDN(matchedDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStartTime()
+  {
+    operation.setProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStopTime()
+  {
+    operation.setProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setReferralURLs(List<String> referralURLs)
+  {
+    operation.setReferralURLs(referralURLs);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResponseData(DirectoryException directoryException)
+  {
+    operation.setResponseData(directoryException);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResultCode(ResultCode resultCode)
+  {
+    operation.setResultCode(resultCode);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSynchronizationOperation(boolean isSynchronizationOperation)
+  {
+    operation.setSynchronizationOperation(isSynchronizationOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void toString(StringBuilder buffer)
+  {
+    operation.toString(buffer);
+  }
+
+}
+
diff --git a/opends/src/server/org/opends/server/core/PluginConfigManager.java b/opends/src/server/org/opends/server/core/PluginConfigManager.java
index 12b2140..cde00ad 100644
--- a/opends/src/server/org/opends/server/core/PluginConfigManager.java
+++ b/opends/src/server/org/opends/server/core/PluginConfigManager.java
@@ -82,21 +82,25 @@
 import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.operation.PostOperationAddOperation;
 import org.opends.server.types.operation.PostOperationBindOperation;
+import org.opends.server.types.operation.PostOperationCompareOperation;
 import org.opends.server.types.operation.PostOperationDeleteOperation;
 import org.opends.server.types.operation.PostOperationModifyOperation;
 import org.opends.server.types.operation.PostOperationSearchOperation;
 import org.opends.server.types.operation.PostResponseAddOperation;
 import org.opends.server.types.operation.PostResponseBindOperation;
+import org.opends.server.types.operation.PostResponseCompareOperation;
 import org.opends.server.types.operation.PostResponseDeleteOperation;
 import org.opends.server.types.operation.PostResponseModifyOperation;
 import org.opends.server.types.operation.PostResponseSearchOperation;
 import org.opends.server.types.operation.PreOperationAddOperation;
 import org.opends.server.types.operation.PreOperationBindOperation;
+import org.opends.server.types.operation.PreOperationCompareOperation;
 import org.opends.server.types.operation.PreOperationDeleteOperation;
 import org.opends.server.types.operation.PreOperationModifyOperation;
 import org.opends.server.types.operation.PreOperationSearchOperation;
 import org.opends.server.types.operation.PreParseAddOperation;
 import org.opends.server.types.operation.PreParseBindOperation;
+import org.opends.server.types.operation.PreParseCompareOperation;
 import org.opends.server.types.operation.PreParseDeleteOperation;
 import org.opends.server.types.operation.PreParseModifyOperation;
 import org.opends.server.types.operation.PreParseSearchOperation;
@@ -1971,7 +1975,7 @@
    * @return  The result of processing the pre-parse compare plugins.
    */
   public PreParsePluginResult invokePreParseComparePlugins(
-                                   CompareOperation compareOperation)
+    PreParseCompareOperation compareOperation)
   {
     PreParsePluginResult result = null;
 
@@ -2705,7 +2709,7 @@
    * @return  The result of processing the pre-operation compare plugins.
    */
   public PreOperationPluginResult invokePreOperationComparePlugins(
-                                       CompareOperation compareOperation)
+     PreOperationCompareOperation compareOperation)
   {
     PreOperationPluginResult result = null;
 
@@ -3439,7 +3443,7 @@
    * @return  The result of processing the post-operation compare plugins.
    */
   public PostOperationPluginResult invokePostOperationComparePlugins(
-                                        CompareOperation compareOperation)
+      PostOperationCompareOperation compareOperation)
   {
     PostOperationPluginResult result = null;
 
@@ -4161,7 +4165,7 @@
    * @return  The result of processing the post-response compare plugins.
    */
   public PostResponsePluginResult invokePostResponseComparePlugins(
-                                       CompareOperation compareOperation)
+      PostResponseCompareOperation compareOperation)
   {
     PostResponsePluginResult result = null;
 
diff --git a/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java b/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
index 1b6e445..448f6fa 100644
--- a/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
+++ b/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
@@ -954,8 +954,8 @@
                                          String attributeType,
                                          ByteString assertionValue)
   {
-    CompareOperation compareOperation =
-         new CompareOperation(this, nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(this, nextOperationID(),
                               nextMessageID(),
                               new ArrayList<Control>(0), rawEntryDN,
                               attributeType, assertionValue);
@@ -985,8 +985,8 @@
                                          AttributeType attributeType,
                                          ByteString assertionValue)
   {
-    CompareOperation compareOperation =
-         new CompareOperation(this, nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(this, nextOperationID(),
                               nextMessageID(),
                               new ArrayList<Control>(0), entryDN,
                               attributeType, assertionValue);
diff --git a/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java b/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
index d872b3b..ce06fad 100644
--- a/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
+++ b/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
@@ -589,8 +589,8 @@
                                         String attributeType,
                                         ASN1OctetString assertionValue)
   {
-    CompareOperation compareOperation =
-         new CompareOperation(this, nextOperationID(), nextMessageID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(this, nextOperationID(), nextMessageID(),
                               new ArrayList<Control>(0), rawEntryDN,
                               attributeType, assertionValue);
 
diff --git a/opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java b/opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
index 41072fb..518f834 100644
--- a/opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
+++ b/opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
@@ -56,7 +56,7 @@
 import org.opends.server.core.AbandonOperation;
 import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.BindOperationBasis;
-import org.opends.server.core.CompareOperation;
+import org.opends.server.core.CompareOperationBasis;
 import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ExtendedOperation;
@@ -2063,8 +2063,8 @@
     }
 
     CompareRequestProtocolOp protocolOp = message.getCompareRequestProtocolOp();
-    CompareOperation compareOp =
-         new CompareOperation(this, nextOperationID.getAndIncrement(),
+    CompareOperationBasis compareOp =
+         new CompareOperationBasis(this, nextOperationID.getAndIncrement(),
                               message.getMessageID(), controls,
                               protocolOp.getDN(), protocolOp.getAttributeType(),
                               protocolOp.getAssertionValue());
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java
new file mode 100644
index 0000000..947b3cc
--- /dev/null
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java
@@ -0,0 +1,86 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.workflowelement.localbackend;
+
+
+import org.opends.server.core.CompareOperation;
+import org.opends.server.core.CompareOperationWrapper;
+import org.opends.server.types.Entry;
+import org.opends.server.types.operation.PostOperationCompareOperation;
+import org.opends.server.types.operation.PostResponseCompareOperation;
+import org.opends.server.types.operation.PreOperationCompareOperation;
+
+
+/**
+ * This class defines an operation that may be used to determine whether a
+ * specified entry in the Directory Server contains a given attribute-value
+ * pair.
+ */
+public class LocalBackendCompareOperation extends CompareOperationWrapper
+    implements PreOperationCompareOperation,
+               PostOperationCompareOperation,
+               PostResponseCompareOperation
+{
+  // The entry to be compared.
+  private Entry entry = null;
+
+
+  /**
+   * Creates a new compare operation based on the provided compare operation.
+   *
+   * @param compare  the compare operation
+   */
+  public LocalBackendCompareOperation(CompareOperation compare)
+  {
+    super(compare);
+    LocalBackendWorkflowElement.attachLocalOperation (compare, this);
+  }
+
+
+  /**
+   * Retrieves the entry to target with the compare operation.
+   *
+   * @return  The entry to target with the compare operation, or
+   *          <CODE>null</CODE> if the entry is not yet available.
+   */
+  public Entry getEntryToCompare()
+  {
+    return entry;
+  }
+
+
+  /**
+   * Set the entry to target with the compare operation.
+   *
+   * @param  entry   The entry to target with the compare operation.
+   */
+  public void setEntryToCompare(Entry entry)
+  {
+    this.entry = entry;
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
index e7919f2..5f95e28 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -74,6 +74,7 @@
 import org.opends.server.core.AccessControlConfigManager;
 import org.opends.server.core.AddOperation;
 import org.opends.server.core.BindOperation;
+import org.opends.server.core.CompareOperation;
 import org.opends.server.core.DeleteOperation;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyOperation;
@@ -178,6 +179,9 @@
     case MODIFY:
       processModify((ModifyOperation) operation);
       break;
+    case COMPARE:
+      processCompare((CompareOperation) operation);
+      break;
     default:
       // jdemendi - temporary code, just make sure that we don't fell into
       // that incomplete code...
@@ -5860,9 +5864,9 @@
 
 
   /**
-   * Perform a delete operation against a local backend.
+   * Performs a delete operation against a local backend.
    *
-   * @param operation - The operation to perform
+   * @param operation the operation to perform
    */
   public void processDelete(DeleteOperation operation){
     LocalBackendDeleteOperation localOperation =
@@ -5871,9 +5875,9 @@
   }
 
   /**
-   * Perform a local delete operation against a local backend.
+   * Performs a local delete operation against a local backend.
    *
-   * @param operation - The operation to perform
+   * @param operation the operation to perform
    */
   private void processLocalDelete(LocalBackendDeleteOperation localOp)
   {
@@ -6673,6 +6677,576 @@
 
 
   /**
+   * Perform a compare operation against a local backend.
+   *
+   * @param operation - The operation to perform
+   */
+  public void processCompare(CompareOperation operation)
+  {
+    LocalBackendCompareOperation localOperation =
+      new LocalBackendCompareOperation(operation);
+    processLocalCompare(localOperation);
+  }
+
+
+  /**
+   * Perform a local compare operation against a local backend.
+   *
+   * @param operation - The operation to perform
+   */
+  private void processLocalCompare(LocalBackendCompareOperation localOp)
+  {
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+         DirectoryServer.getPluginConfigManager();
+    boolean skipPostOperation = false;
+
+
+    // Get a reference to the client connection
+    ClientConnection clientConnection = localOp.getClientConnection();
+
+
+    // Check for and handle a request to cancel this operation.
+    if (localOp.getCancelRequest() != null)
+    {
+      return;
+    }
+
+
+    // Create a labeled block of code that we can break out of if a problem is
+    // detected.
+compareProcessing:
+    {
+      // Process the entry DN to convert it from the raw form to the form
+      // required for the rest of the compare processing.
+      DN entryDN = localOp.getEntryDN();
+      if (entryDN == null)
+      {
+        skipPostOperation = true;
+        break compareProcessing;
+      }
+
+
+      // If the target entry is in the server configuration, then make sure the
+      // requester has the CONFIG_READ privilege.
+      if (DirectoryServer.getConfigHandler().handlesEntry(entryDN) &&
+          (! clientConnection.hasPrivilege(Privilege.CONFIG_READ, localOp)))
+      {
+        int msgID = MSGID_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES;
+        localOp.appendErrorMessage(getMessage(msgID));
+        localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+        skipPostOperation = true;
+        break compareProcessing;
+      }
+
+
+      // Check for and handle a request to cancel this operation.
+      if (localOp.getCancelRequest() != null)
+      {
+        return;
+      }
+
+
+      // Grab a read lock on the entry.
+      Lock readLock = null;
+      for (int i=0; i < 3; i++)
+      {
+        readLock = LockManager.lockRead(entryDN);
+        if (readLock != null)
+        {
+          break;
+        }
+      }
+
+      if (readLock == null)
+      {
+        int    msgID   = MSGID_COMPARE_CANNOT_LOCK_ENTRY;
+        String message = getMessage(msgID, String.valueOf(entryDN));
+
+        localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
+        localOp.appendErrorMessage(message);
+        skipPostOperation = true;
+        break compareProcessing;
+      }
+
+      Entry entry = null;
+      try
+      {
+        // Get the entry.  If it does not exist, then fail.
+        try
+        {
+          entry = DirectoryServer.getEntry(entryDN);
+          localOp.setEntryToCompare(entry);
+
+          if (entry == null)
+          {
+            localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
+            localOp.appendErrorMessage(getMessage(MSGID_COMPARE_NO_SUCH_ENTRY,
+                                          String.valueOf(entryDN)));
+
+            // See if one of the entry's ancestors exists.
+            DN parentDN = entryDN.getParentDNInSuffix();
+            while (parentDN != null)
+            {
+              try
+              {
+                if (DirectoryServer.entryExists(parentDN))
+                {
+                  localOp.setMatchedDN(parentDN);
+                  break;
+                }
+              }
+              catch (Exception e)
+              {
+                if (debugEnabled())
+                {
+                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
+                }
+                break;
+              }
+
+              parentDN = parentDN.getParentDNInSuffix();
+            }
+
+            break compareProcessing;
+          }
+        }
+        catch (DirectoryException de)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, de);
+          }
+
+          localOp.setResultCode(de.getResultCode());
+          localOp.appendErrorMessage(de.getErrorMessage());
+          break compareProcessing;
+        }
+
+        // Check to see if there are any controls in the request.  If so, then
+        // see if there is any special processing required.
+        List<Control> requestControls = localOp.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);
+                  }
+
+                  localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+                  localOp.appendErrorMessage(le.getMessage());
+
+                  break compareProcessing;
+                }
+              }
+
+              try
+              {
+                // FIXME -- We need to determine whether the current user has
+                //          permission to make this determination.
+                SearchFilter filter = assertControl.getSearchFilter();
+                if (! filter.matchesEntry(entry))
+                {
+                  localOp.setResultCode(ResultCode.ASSERTION_FAILED);
+
+                  localOp.appendErrorMessage(
+                      getMessage(MSGID_COMPARE_ASSERTION_FAILED,
+                      String.valueOf(entryDN)));
+
+                  break compareProcessing;
+                }
+              }
+              catch (DirectoryException de)
+              {
+                if (debugEnabled())
+                {
+                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
+                }
+
+                localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
+
+                int msgID = MSGID_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER;
+                localOp.appendErrorMessage(
+                    getMessage(msgID, String.valueOf(entryDN),
+                    de.getErrorMessage()));
+
+                break compareProcessing;
+              }
+            }
+            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, localOp))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                localOp.appendErrorMessage(getMessage(msgID));
+                localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break compareProcessing;
+              }
+
+
+              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);
+                  }
+
+                  localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+                  localOp.appendErrorMessage(le.getMessage());
+
+                  break compareProcessing;
+                }
+              }
+
+
+              Entry authorizationEntry;
+              try
+              {
+                authorizationEntry = proxyControl.getAuthorizationEntry();
+              }
+              catch (DirectoryException de)
+              {
+                if (debugEnabled())
+                {
+                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
+                }
+
+                localOp.setResultCode(de.getResultCode());
+                localOp.appendErrorMessage(de.getErrorMessage());
+
+                break compareProcessing;
+              }
+
+              if (AccessControlConfigManager.getInstance()
+                      .getAccessControlHandler().isProxiedAuthAllowed(localOp,
+                                                 authorizationEntry) == false) {
+                localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+                int msgID = MSGID_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+                localOp.appendErrorMessage(
+                    getMessage(msgID, String.valueOf(entryDN)));
+
+                skipPostOperation = true;
+                break compareProcessing;
+              }
+              localOp.setAuthorizationEntry(authorizationEntry);
+              if (authorizationEntry == null)
+              {
+                localOp.setProxiedAuthorizationDN(DN.nullDN());
+              }
+              else
+              {
+                localOp.setProxiedAuthorizationDN(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, localOp))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                localOp.appendErrorMessage(getMessage(msgID));
+                localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
+                break compareProcessing;
+              }
+
+
+              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);
+                  }
+
+                  localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
+                  localOp.appendErrorMessage(le.getMessage());
+
+                  break compareProcessing;
+                }
+              }
+
+
+              Entry authorizationEntry;
+              try
+              {
+                authorizationEntry = proxyControl.getAuthorizationEntry();
+              }
+              catch (DirectoryException de)
+              {
+                if (debugEnabled())
+                {
+                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
+                }
+
+                localOp.setResultCode(de.getResultCode());
+                localOp.appendErrorMessage(de.getErrorMessage());
+
+                break compareProcessing;
+              }
+
+              if (AccessControlConfigManager.getInstance()
+                      .getAccessControlHandler().isProxiedAuthAllowed(localOp,
+                                                 authorizationEntry) == false) {
+                localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+                int msgID = MSGID_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+                localOp.appendErrorMessage(
+                    getMessage(msgID, String.valueOf(entryDN)));
+
+                skipPostOperation = true;
+                break compareProcessing;
+              }
+              localOp.setAuthorizationEntry(authorizationEntry);
+              if (authorizationEntry == null)
+              {
+                localOp.setProxiedAuthorizationDN(DN.nullDN());
+              }
+              else
+              {
+                localOp.setProxiedAuthorizationDN(authorizationEntry.getDN());
+              }
+            }
+
+            // NYI -- Add support for additional controls.
+            else if (c.isCritical())
+            {
+              Backend backend = DirectoryServer.getBackend(entryDN);
+              if ((backend == null) || (! backend.supportsControl(oid)))
+              {
+                localOp.setResultCode(
+                    ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
+
+                int msgID = MSGID_COMPARE_UNSUPPORTED_CRITICAL_CONTROL;
+                localOp.appendErrorMessage(
+                    getMessage(msgID, String.valueOf(entryDN), oid));
+
+                break compareProcessing;
+              }
+            }
+          }
+        }
+
+
+        // Check to see if the client has permission to perform the
+        // compare.
+
+        // FIXME: for now assume that this will check all permission
+        // pertinent to the operation. This includes proxy authorization
+        // and any other controls specified.
+
+        // FIXME: earlier checks to see if the entry already exists may
+        // have already exposed sensitive information to the client.
+        if (AccessControlConfigManager.getInstance()
+            .getAccessControlHandler().isAllowed(localOp) == false) {
+          localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+          int msgID = MSGID_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+          localOp.appendErrorMessage(
+              getMessage(msgID, String.valueOf(entryDN)));
+
+          skipPostOperation = true;
+          break compareProcessing;
+        }
+
+        // Check for and handle a request to cancel this operation.
+        if (localOp.getCancelRequest() != null)
+        {
+          return;
+        }
+
+
+        // Invoke the pre-operation compare plugins.
+        PreOperationPluginResult preOpResult =
+             pluginConfigManager.invokePreOperationComparePlugins(localOp);
+        if (preOpResult.connectionTerminated())
+        {
+          // There's no point in continuing with anything.  Log the request and
+          // result and return.
+          localOp.setResultCode(ResultCode.CANCELED);
+
+          int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+          localOp.appendErrorMessage(getMessage(msgID));
+
+          return;
+        }
+        else if (preOpResult.sendResponseImmediately())
+        {
+          skipPostOperation = true;
+          break compareProcessing;
+        }
+        else if (preOpResult.skipCoreProcessing())
+        {
+          skipPostOperation = false;
+          break compareProcessing;
+        }
+
+
+        // Get the base attribute type and set of options.
+        String          baseName;
+        HashSet<String> options;
+        String rawAttributeType = localOp.getRawAttributeType();
+        int             semicolonPos = rawAttributeType.indexOf(';');
+        if (semicolonPos > 0)
+        {
+          baseName = toLowerCase(rawAttributeType.substring(0, semicolonPos));
+
+          options = new HashSet<String>();
+          int nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
+          while (nextPos > 0)
+          {
+            options.add(rawAttributeType.substring(semicolonPos+1, nextPos));
+            semicolonPos = nextPos;
+            nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
+          }
+
+          options.add(rawAttributeType.substring(semicolonPos+1));
+        }
+        else
+        {
+          baseName = toLowerCase(rawAttributeType);
+          options  = null;
+        }
+
+
+        // Actually perform the compare operation.
+        List<Attribute> attrList = null;
+        if (localOp.getAttributeType() == null)
+        {
+          localOp.setAttributeType(DirectoryServer.getAttributeType(baseName));
+        }
+        if (localOp.getAttributeType() == null)
+        {
+          attrList = entry.getAttribute(baseName, options);
+          localOp.setAttributeType(
+              DirectoryServer.getDefaultAttributeType(baseName));
+        }
+        else
+        {
+          attrList = entry.getAttribute(localOp.getAttributeType(), options);
+        }
+
+        if ((attrList == null) || attrList.isEmpty())
+        {
+          localOp.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
+          if (options == null)
+          {
+            localOp.appendErrorMessage(getMessage(MSGID_COMPARE_OP_NO_SUCH_ATTR,
+                                          String.valueOf(entryDN), baseName));
+          }
+          else
+          {
+            localOp.appendErrorMessage(getMessage(
+                                    MSGID_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS,
+                                    String.valueOf(entryDN), baseName));
+          }
+        }
+        else
+        {
+          AttributeValue value = new AttributeValue(
+              localOp.getAttributeType(),
+              localOp.getAssertionValue());
+
+          boolean matchFound = false;
+          for (Attribute a : attrList)
+          {
+            if (a.hasValue(value))
+            {
+              matchFound = true;
+              break;
+            }
+          }
+
+          if (matchFound)
+          {
+            localOp.setResultCode(ResultCode.COMPARE_TRUE);
+          }
+          else
+          {
+            localOp.setResultCode(ResultCode.COMPARE_FALSE);
+          }
+        }
+      }
+      finally
+      {
+        LockManager.unlock(entryDN, readLock);
+      }
+    }
+
+
+    // Check for and handle a request to cancel this operation.
+    if (localOp.getCancelRequest() != null)
+    {
+      return;
+    }
+
+
+    // Invoke the post-operation compare plugins.
+    if (! skipPostOperation)
+    {
+      PostOperationPluginResult postOperationResult =
+           pluginConfigManager.invokePostOperationComparePlugins(localOp);
+      if (postOperationResult.connectionTerminated())
+      {
+        localOp.setResultCode(ResultCode.CANCELED);
+
+        int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
+        localOp.appendErrorMessage(getMessage(msgID));
+
+        return;
+      }
+    }
+  }
+
+
+  /**
    * Attaches the current local operation to the global operation so that
    * operation runner can execute local operation post response later on.
    *
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java
index 77b150f..d6ad1d6 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/CompareOperationTestCase.java
@@ -48,7 +48,6 @@
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 import static org.testng.Assert.*;
-import static org.testng.Assert.assertEquals;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -138,7 +137,8 @@
 
     return new Operation[]
     {
-      new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+      new CompareOperationBasis(
+                           conn, InternalClientConnection.nextOperationID(),
                            InternalClientConnection.nextMessageID(),
                            new ArrayList<Control>(),
                            new ASN1OctetString(entry.getDN().toString()),
@@ -216,8 +216,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString(entry.getDN().toString()),
@@ -240,8 +241,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString(entry.getDN().toString()),
@@ -263,8 +265,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString("o=nonexistent,o=test"),
@@ -285,8 +288,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString("rogasawara,o=test"),
@@ -307,8 +311,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString(entry.getDN().toString()),
@@ -330,8 +335,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString(entry.getDN().toString()),
@@ -352,8 +358,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString(entry.getDN().toString()),
@@ -371,8 +378,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString(entry.getDN().toString()),
@@ -390,8 +398,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString(entry.getDN().toString()),
@@ -409,8 +418,9 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               new ArrayList<Control>(),
                               new ASN1OctetString(entry.getDN().toString()),
@@ -436,8 +446,9 @@
     List<Control> controls = new ArrayList<Control>();
     controls.add(assertControl);
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
                               new ASN1OctetString(entry.getDN().toString()),
@@ -465,8 +476,9 @@
     List<Control> controls = new ArrayList<Control>();
     controls.add(assertControl);
 
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
                               new ASN1OctetString(entry.getDN().toString()),
@@ -490,8 +502,9 @@
     List<Control> controls = new ArrayList<Control>();
     controls.add(authV1Control);
 
-    CompareOperation compareOperation =
-         new CompareOperation(proxyUserConn,
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              proxyUserConn,
                               InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
@@ -518,8 +531,9 @@
     List<Control> controls = new ArrayList<Control>();
     controls.add(authV1Control);
 
-    CompareOperation compareOperation =
-         new CompareOperation(proxyUserConn,
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              proxyUserConn,
                               InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
@@ -544,8 +558,9 @@
     List<Control> controls = new ArrayList<Control>();
     controls.add(authV2Control);
 
-    CompareOperation compareOperation =
-         new CompareOperation(proxyUserConn,
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              proxyUserConn,
                               InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
@@ -570,8 +585,9 @@
     List<Control> controls = new ArrayList<Control>();
     controls.add(authV2Control);
 
-    CompareOperation compareOperation =
-         new CompareOperation(proxyUserConn,
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              proxyUserConn,
                               InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
@@ -597,8 +613,9 @@
     List<Control> controls = new ArrayList<Control>();
     controls.add(authV2Control);
 
-    CompareOperation compareOperation =
-         new CompareOperation(proxyUserConn,
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              proxyUserConn,
                               InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
@@ -625,8 +642,9 @@
          new LDAPAssertionRequestControl("1.1.1.1.1.1", true, ldapFilter);
     List<Control> controls = new ArrayList<Control>();
     controls.add(assertControl);
-    CompareOperation compareOperation =
-         new CompareOperation(conn, InternalClientConnection.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(
+                              conn, InternalClientConnection.nextOperationID(),
                               InternalClientConnection.nextMessageID(),
                               controls,
                               new ASN1OctetString(entry.getDN().toString()),
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java
index fd0d383..2e259ff 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxPrivilegeTestCase.java
@@ -44,7 +44,6 @@
 import java.util.UUID;
 
 import org.opends.server.TestCaseUtils;
-import org.opends.server.admin.ClassLoaderProvider;
 import org.opends.server.backends.task.Task;
 import org.opends.server.backends.task.TaskBackend;
 import org.opends.server.backends.task.TaskState;
@@ -53,6 +52,7 @@
 import org.opends.server.core.AddOperation;
 import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.CompareOperation;
+import org.opends.server.core.CompareOperationBasis;
 import org.opends.server.core.DeleteOperation;
 import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.core.DirectoryServer;
@@ -1260,8 +1260,8 @@
 
 
     // Test a compare operation against the PWReset Target user.
-    CompareOperation compareOperation =
-         new CompareOperation(conn, conn.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(conn, conn.nextOperationID(),
                               conn.nextMessageID(), controls, targetDN,
                               DirectoryServer.getAttributeType("cn", true),
                               ByteStringFactory.create("PWReset Target"));
@@ -1467,8 +1467,8 @@
 
 
     // Test a compare operation against the PWReset Target user.
-    CompareOperation compareOperation =
-         new CompareOperation(conn, conn.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(conn, conn.nextOperationID(),
                               conn.nextMessageID(), controls, targetDN,
                               DirectoryServer.getAttributeType("cn", true),
                               ByteStringFactory.create("PWReset Target"));
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
index 87e4bf6..8eea58b 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
@@ -48,6 +48,7 @@
 import org.opends.server.core.AddOperation;
 import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.CompareOperation;
+import org.opends.server.core.CompareOperationBasis;
 import org.opends.server.core.DeleteOperation;
 import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.core.DirectoryServer;
@@ -1319,8 +1320,8 @@
 
 
     // Test a compare operation against the PWReset Target user.
-    CompareOperation compareOperation =
-         new CompareOperation(conn, conn.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(conn, conn.nextOperationID(),
                               conn.nextMessageID(), controls, targetDN,
                               DirectoryServer.getAttributeType("cn", true),
                               ByteStringFactory.create("PWReset Target"));
@@ -1524,8 +1525,8 @@
 
 
     // Test a compare operation against the PWReset Target user.
-    CompareOperation compareOperation =
-         new CompareOperation(conn, conn.nextOperationID(),
+    CompareOperationBasis compareOperation =
+         new CompareOperationBasis(conn, conn.nextOperationID(),
                               conn.nextMessageID(), controls, targetDN,
                               DirectoryServer.getAttributeType("cn", true),
                               ByteStringFactory.create("PWReset Target"));

--
Gitblit v1.10.0