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

Jean-Noel Rouvignac
15.17.2013 cab0b20204866c68b0cce38b4492f707187cc128
Refactoring on the LocalBackend*Operation classes:
- Extracted process*() methods from the processLocal*() methods for the LocalBackend*Operation classes.
- Transformed plain comments into javadocs.
- Used interfaces instead of concrete classes.
- Made all protected members be private.
3 files modified
1660 ■■■■ changed files
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java 447 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java 841 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java 372 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java
@@ -68,22 +68,22 @@
  /**
   * The backend in which the comparison is to be performed.
   */
  protected Backend backend;
  private Backend backend;
  /**
   * The client connection for this operation.
   */
  protected ClientConnection clientConnection;
  private ClientConnection clientConnection;
  /**
   * The DN of the entry to compare.
   */
  protected DN entryDN;
  private DN entryDN;
  /**
   * The entry to be compared.
   */
  protected Entry entry = null;
  private Entry entry;
@@ -125,242 +125,26 @@
  public void processLocalCompare(LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
    this.backend = wfe.getBackend();
    clientConnection  = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    // Get a reference to the client connection
    ClientConnection clientConnection = getClientConnection();
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // 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.
      entryDN = getEntryDN();
      if (entryDN == null)
      {
        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)))
      {
        appendErrorMessage(ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get());
        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        break compareProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Grab a read lock on the entry.
      final Lock readLock = LockManager.lockRead(entryDN);
      if (readLock == null)
      {
        setResultCode(ResultCode.BUSY);
        appendErrorMessage(ERR_COMPARE_CANNOT_LOCK_ENTRY.get(
                                String.valueOf(entryDN)));
        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(
                    ERR_COMPARE_NO_SUCH_ENTRY.get(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.getMessageObject());
          break compareProcessing;
        }
        // Check to see if there are any controls in the request.  If so, then
        // see if there is any special processing required.
        try
        {
          handleRequestControls();
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          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.
        try
        {
          if (!AccessControlConfigManager.getInstance()
              .getAccessControlHandler().isAllowed(this))
          {
            setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
            appendErrorMessage(ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
                .get(String.valueOf(entryDN)));
            break compareProcessing;
          }
        }
        catch (DirectoryException e)
        {
          setResultCode(e.getResultCode());
          appendErrorMessage(e.getMessageObject());
          break compareProcessing;
        }
        // Check for a request to cancel this operation.
        checkIfCanceled(false);
        // Invoke the pre-operation compare plugins.
        executePostOpPlugins = true;
        PluginResult.PreOperation preOpResult =
             pluginConfigManager.invokePreOperationComparePlugins(this);
          if (!preOpResult.continueProcessing())
          {
            setResultCode(preOpResult.getResultCode());
            appendErrorMessage(preOpResult.getErrorMessage());
            setMatchedDN(preOpResult.getMatchedDN());
            setReferralURLs(preOpResult.getReferralURLs());
            break compareProcessing;
          }
        // Get the base attribute type and set of options.
        Set<String> options = getAttributeOptions();
        // Actually perform the compare operation.
        AttributeType attrType = getAttributeType();
        List<Attribute> attrList = entry.getAttribute(attrType, options);
        if ((attrList == null) || attrList.isEmpty())
        {
          setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
          if (options == null)
          {
            appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR.get(
                                    String.valueOf(entryDN),
                                    getRawAttributeType()));
          }
          else
          {
            appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS.get(
                                    String.valueOf(entryDN),
                                    getRawAttributeType()));
          }
        }
        else
        {
          AttributeValue value = AttributeValues.create(attrType,
                                                    getAssertionValue());
          boolean matchFound = false;
          for (Attribute a : attrList)
          {
            if (a.contains(value))
            {
              matchFound = true;
              break;
            }
          }
          if (matchFound)
          {
            setResultCode(ResultCode.COMPARE_TRUE);
          }
          else
          {
            setResultCode(ResultCode.COMPARE_FALSE);
          }
        }
      }
      finally
      {
        LockManager.unlock(entryDN, readLock);
      }
    }
    BooleanHolder executePostOpPlugins = new BooleanHolder();
    processCompare(executePostOpPlugins);
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Invoke the post-operation compare plugins.
    if (executePostOpPlugins)
    if (executePostOpPlugins.value)
    {
      PluginResult.PostOperation postOpResult =
           pluginConfigManager.invokePostOperationComparePlugins(this);
          DirectoryServer.getPluginConfigManager()
              .invokePostOperationComparePlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
@@ -371,23 +155,223 @@
    }
  }
  private void processCompare(BooleanHolder executePostOpPlugins)
      throws CanceledOperationException
  {
    // Process the entry DN to convert it from the raw form to the form
    // required for the rest of the compare processing.
    entryDN = getEntryDN();
    if (entryDN == null)
    {
      return;
    }
    // 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)))
    {
      appendErrorMessage(ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get());
      setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
      return;
    }
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Grab a read lock on the entry.
    final Lock readLock = LockManager.lockRead(entryDN);
    if (readLock == null)
    {
      setResultCode(ResultCode.BUSY);
      appendErrorMessage(ERR_COMPARE_CANNOT_LOCK_ENTRY.get(String
          .valueOf(entryDN)));
      return;
    }
    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(ERR_COMPARE_NO_SUCH_ENTRY.get(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();
          }
          return;
        }
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResultCode(de.getResultCode());
        appendErrorMessage(de.getMessageObject());
        return;
      }
      // Check to see if there are any controls in the request. If so, then
      // see if there is any special processing required.
      try
      {
        handleRequestControls();
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResponseData(de);
        return;
      }
      // 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.
      try
      {
        if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
            .isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
              .get(String.valueOf(entryDN)));
          return;
        }
      }
      catch (DirectoryException e)
      {
        setResultCode(e.getResultCode());
        appendErrorMessage(e.getMessageObject());
        return;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Invoke the pre-operation compare plugins.
      executePostOpPlugins.value = true;
      PluginResult.PreOperation preOpResult =
          DirectoryServer.getPluginConfigManager()
              .invokePreOperationComparePlugins(this);
      if (!preOpResult.continueProcessing())
      {
        setResultCode(preOpResult.getResultCode());
        appendErrorMessage(preOpResult.getErrorMessage());
        setMatchedDN(preOpResult.getMatchedDN());
        setReferralURLs(preOpResult.getReferralURLs());
        return;
      }
      // Get the base attribute type and set of options.
      Set<String> options = getAttributeOptions();
      // Actually perform the compare operation.
      AttributeType attrType = getAttributeType();
      List<Attribute> attrList = entry.getAttribute(attrType, options);
      if ((attrList == null) || attrList.isEmpty())
      {
        setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
        if (options == null)
        {
          appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR.get(String
              .valueOf(entryDN), getRawAttributeType()));
        }
        else
        {
          appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS.get(
              String.valueOf(entryDN), getRawAttributeType()));
        }
      }
      else
      {
        AttributeValue value =
            AttributeValues.create(attrType, getAssertionValue());
        boolean matchFound = false;
        for (Attribute a : attrList)
        {
          if (a.contains(value))
          {
            matchFound = true;
            break;
          }
        }
        if (matchFound)
        {
          setResultCode(ResultCode.COMPARE_TRUE);
        }
        else
        {
          setResultCode(ResultCode.COMPARE_FALSE);
        }
      }
    }
    finally
    {
      LockManager.unlock(entryDN, readLock);
    }
  }
  /**
   * Performs any processing required for the controls included in the request.
   *
   * @throws  DirectoryException  If a problem occurs that should prevent the
   *                              operation from succeeding.
   */
  protected void handleRequestControls()
          throws DirectoryException
  private void handleRequestControls() throws DirectoryException
  {
    List<Control> requestControls = getRequestControls();
    if ((requestControls != null) && (! requestControls.isEmpty()))
    {
      for (int i=0; i < requestControls.size(); i++)
      for (Control c : requestControls)
      {
        Control c   = requestControls.get(i);
        String  oid = c.getOID();
        if (!LocalBackendWorkflowElement.isControlAllowed(entryDN, this, c))
@@ -462,7 +446,7 @@
          addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
              "obsoleteProxiedAuthzV1Control"));
          // The requester must have the PROXIED_AUTH privilige in order to
          // The requester must have the PROXIED_AUTH privilege in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
@@ -486,7 +470,7 @@
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // The requester must have the PROXIED_AUTH privilege in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
@@ -524,4 +508,3 @@
    }
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -74,43 +74,43 @@
  /**
   * The backend in which the operation is to be processed.
   */
  protected Backend backend;
  private Backend backend;
  /**
   * Indicates whether the no-op control was included in the request.
   */
  protected boolean noOp;
  private boolean noOp;
  /**
   * The client connection on which this operation was requested.
   */
  protected ClientConnection clientConnection;
  private ClientConnection clientConnection;
  /**
   * The original DN of the entry.
   */
  protected DN entryDN;
  private DN entryDN;
  /**
   * The current entry, before it is renamed.
   */
  protected Entry currentEntry;
  private Entry currentEntry;
  /**
   * The new entry, as it will appear after it has been renamed.
   */
  protected Entry newEntry;
  private Entry newEntry;
  // The LDAP post-read request control, if present in the request.
  /** The LDAP post-read request control, if present in the request. */
  private LDAPPostReadRequestControl postReadRequest;
  // The LDAP pre-read request control, if present in the request.
  /** The LDAP pre-read request control, if present in the request. */
  private LDAPPreReadRequestControl preReadRequest;
  /**
   * The new RDN for the entry.
   */
  protected RDN newRDN;
  private RDN newRDN;
@@ -171,447 +171,19 @@
  public void processLocalModifyDN(final LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
    this.backend = wfe.getBackend();
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
modifyDNProcessing:
    {
      // Process the entry DN, newRDN, and newSuperior elements from their raw
      // forms as provided by the client to the forms required for the rest of
      // the modify DN processing.
      entryDN = getEntryDN();
      newRDN = getNewRDN();
      if (newRDN == null)
      {
        break modifyDNProcessing;
      }
      DN newSuperior = getNewSuperior();
      if ((newSuperior == null) &&
          (getRawNewSuperior() != null))
      {
        break modifyDNProcessing;
      }
      // Construct the new DN to use for the entry.
      DN parentDN;
      if (newSuperior == null)
      {
        parentDN = entryDN.getParentDNInSuffix();
      }
      else
      {
        if(newSuperior.isDescendantOf(entryDN))
        {
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_MODDN_NEW_SUPERIOR_IN_SUBTREE.get(
              String.valueOf(entryDN), String.valueOf(newSuperior)));
          break modifyDNProcessing;
        }
        parentDN = newSuperior;
      }
      if ((parentDN == null) || parentDN.isNullDN())
      {
        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        appendErrorMessage(ERR_MODDN_NO_PARENT.get(String.valueOf(entryDN)));
        break modifyDNProcessing;
      }
      DN newDN = parentDN.concat(newRDN);
      // Get the backend for the current entry, and the backend for the new
      // entry.  If either is null, or if they are different, then fail.
      Backend currentBackend = backend;
      if (currentBackend == null)
      {
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get(
                                String.valueOf(entryDN)));
        break modifyDNProcessing;
      }
      Backend newBackend = DirectoryServer.getBackend(newDN);
      if (newBackend == null)
      {
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get(
                                String.valueOf(entryDN),
                                String.valueOf(newDN)));
        break modifyDNProcessing;
      }
      else if (! currentBackend.equals(newBackend))
      {
        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get(
                                String.valueOf(entryDN),
                                String.valueOf(newDN)));
        break modifyDNProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Acquire write locks for the current and new DN.
      final Lock currentLock = LockManager.lockWrite(entryDN);
      if (currentLock == null)
      {
        setResultCode(ResultCode.BUSY);
        appendErrorMessage(ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get(
                                String.valueOf(entryDN)));
        break modifyDNProcessing;
      }
      Lock newLock = null;
      try
      {
        newLock = LockManager.lockWrite(newDN);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        LockManager.unlock(entryDN, currentLock);
        if (newLock != null)
        {
          LockManager.unlock(newDN, newLock);
        }
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_MODDN_EXCEPTION_LOCKING_NEW_DN.get(
                                String.valueOf(entryDN), String.valueOf(newDN),
                                getExceptionMessage(e)));
        break modifyDNProcessing;
      }
      if (newLock == null)
      {
        LockManager.unlock(entryDN, currentLock);
        setResultCode(ResultCode.BUSY);
        appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(
                                String.valueOf(entryDN),
                                String.valueOf(newDN)));
        break modifyDNProcessing;
      }
      try
      {
        // Check for a request to cancel this operation.
        checkIfCanceled(false);
        // Get the current entry from the appropriate backend.  If it doesn't
        // exist, then fail.
        try
        {
          currentEntry = currentBackend.getEntry(entryDN);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
        if (getOriginalEntry() == null)
        {
          // See if one of the entry's ancestors exists.
          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();
          }
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get(
                                  String.valueOf(entryDN)));
          break modifyDNProcessing;
        }
        // Check to see if there are any controls in the request.  If so, then
        // see if there is any special processing required.
        try
        {
          handleRequestControls();
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
        // Check to see if the client has permission to perform the
        // modify DN.
        // 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 or new superior
        // already exists may have already exposed sensitive information
        // to the client.
        try
        {
          if (!AccessControlConfigManager.getInstance()
              .getAccessControlHandler().isAllowed(this))
          {
            setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
            appendErrorMessage(ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
                .get(String.valueOf(entryDN)));
            break modifyDNProcessing;
          }
        }
        catch (DirectoryException e)
        {
          setResultCode(e.getResultCode());
          appendErrorMessage(e.getMessageObject());
          break modifyDNProcessing;
        }
        // Duplicate the entry and set its new DN.  Also, create an empty list
        // to hold the attribute-level modifications.
        newEntry = currentEntry.duplicate(false);
        newEntry.setDN(newDN);
        // init the modifications
        addModification(null);
        List<Modification> modifications = this.getModifications();
        if(!handleConflictResolution()) {
            break modifyDNProcessing;
        }
        // If the operation is not a synchronization operation,
        //  - Apply the RDN changes.
        //  - Invoke the pre-operation modify DN plugins.
        //  - apply additional modifications provided by the plugins.
        // If the operation is a synchronization operation
        //  - apply the operation as it was originally done on the master.
        if (! isSynchronizationOperation())
        {
          // Apply any changes to the entry based on the change in its RDN.
          // Also perform schema checking on the updated entry.
          try
          {
            applyRDNChanges(modifications);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break modifyDNProcessing;
          }
          // Check for a request to cancel this operation.
          checkIfCanceled(false);
          // Get a count of the current number of modifications.  The
          // pre-operation plugins may alter this list, and we need to be able
          // to identify which changes were made after they're done.
          int modCount = modifications.size();
          executePostOpPlugins = true;
          PluginResult.PreOperation preOpResult =
            pluginConfigManager.invokePreOperationModifyDNPlugins(this);
          if (!preOpResult.continueProcessing())
          {
            setResultCode(preOpResult.getResultCode());
            appendErrorMessage(preOpResult.getErrorMessage());
            setMatchedDN(preOpResult.getMatchedDN());
            setReferralURLs(preOpResult.getReferralURLs());
            break modifyDNProcessing;
          }
          // Check to see if any of the pre-operation plugins made any changes
          // to the entry.  If so, then apply them.
          if (modifications.size() > modCount)
          {
            try
            {
              applyPreOpModifications(modifications, modCount, true);
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              setResponseData(de);
              break modifyDNProcessing;
            }
          }
        }
        else
        {
          // This is a synchronization operation
          // Apply the modifications as they were originally done.
          try
          {
            applyRDNChanges(modifications);
            applyPreOpModifications(modifications, 0, false);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break modifyDNProcessing;
          }
        }
        // Actually perform the modify DN operation.
        // This should include taking
        // care of any synchronization that might be needed.
        try
        {
          // If it is not a private backend, then check to see if the server or
          // backend is operating in read-only mode.
          if (! currentBackend.isPrivateBackend())
          {
            switch (DirectoryServer.getWritabilityMode())
            {
              case DISABLED:
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
                                        String.valueOf(entryDN)));
                break modifyDNProcessing;
              case INTERNAL_ONLY:
                if (! (isInternalOperation() || isSynchronizationOperation()))
                {
                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
                                          String.valueOf(entryDN)));
                  break modifyDNProcessing;
                }
            }
            switch (currentBackend.getWritabilityMode())
            {
              case DISABLED:
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
                                        String.valueOf(entryDN)));
                break modifyDNProcessing;
              case INTERNAL_ONLY:
                if (! (isInternalOperation() || isSynchronizationOperation()))
                {
                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
                                          String.valueOf(entryDN)));
                  break modifyDNProcessing;
                }
            }
          }
          if (noOp)
          {
            appendErrorMessage(INFO_MODDN_NOOP.get());
            setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
              if(!processPreOperation()) {
                  break modifyDNProcessing;
              }
              currentBackend.renameEntry(entryDN, newEntry, this);
          }
          // Attach the pre-read and/or post-read controls to the response if
          // appropriate.
          LocalBackendWorkflowElement.addPreReadResponse(this,
              preReadRequest, currentEntry);
          LocalBackendWorkflowElement.addPostReadResponse(this,
              postReadRequest, newEntry);
          if (! noOp)
          {
            setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
      }
      finally
      {
        LockManager.unlock(entryDN, currentLock);
        LockManager.unlock(newDN, newLock);
        processSynchPostOperationPlugins();
      }
    }
    BooleanHolder executePostOpPlugins = new BooleanHolder();
    processModifyDN(executePostOpPlugins);
    // Invoke the post-operation or post-synchronization modify DN plugins.
    PluginConfigManager pluginConfigManager =
        DirectoryServer.getPluginConfigManager();
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
@@ -619,10 +191,10 @@
        pluginConfigManager.invokePostSynchronizationModifyDNPlugins(this);
      }
    }
    else if (executePostOpPlugins)
    else if (executePostOpPlugins.value)
    {
      PluginResult.PostOperation postOpResult =
           pluginConfigManager.invokePostOperationModifyDNPlugins(this);
          pluginConfigManager.invokePostOperationModifyDNPlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
@@ -646,8 +218,8 @@
          // Notify persistent searches.
          for (PersistentSearch psearch : wfe.getPersistentSearches())
          {
            psearch.processModifyDN(newEntry, getChangeNumber(),
                currentEntry.getDN());
            psearch.processModifyDN(newEntry, getChangeNumber(), currentEntry
                .getDN());
          }
          // Notify change listeners.
@@ -666,8 +238,9 @@
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER
                  .get(getExceptionMessage(e));
              Message message =
                  ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER
                      .get(getExceptionMessage(e));
              logError(message);
            }
          }
@@ -676,7 +249,350 @@
    }
  }
  private void processModifyDN(BooleanHolder executePostOpPlugins)
      throws CanceledOperationException
  {
    // Process the entry DN, newRDN, and newSuperior elements from their raw
    // forms as provided by the client to the forms required for the rest of
    // the modify DN processing.
    entryDN = getEntryDN();
    newRDN = getNewRDN();
    if (newRDN == null)
    {
      return;
    }
    DN newSuperior = getNewSuperior();
    if (newSuperior == null && getRawNewSuperior() != null)
    {
      return;
    }
    // Construct the new DN to use for the entry.
    DN parentDN;
    if (newSuperior == null)
    {
      parentDN = entryDN.getParentDNInSuffix();
    }
    else
    {
      if (newSuperior.isDescendantOf(entryDN))
      {
        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        appendErrorMessage(ERR_MODDN_NEW_SUPERIOR_IN_SUBTREE.get(String
            .valueOf(entryDN), String.valueOf(newSuperior)));
        return;
      }
      parentDN = newSuperior;
    }
    if (parentDN == null || parentDN.isNullDN())
    {
      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
      appendErrorMessage(ERR_MODDN_NO_PARENT.get(String.valueOf(entryDN)));
      return;
    }
    DN newDN = parentDN.concat(newRDN);
    // Get the backend for the current entry, and the backend for the new
    // entry. If either is null, or if they are different, then fail.
    Backend currentBackend = backend;
    if (currentBackend == null)
    {
      setResultCode(ResultCode.NO_SUCH_OBJECT);
      appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get(String
          .valueOf(entryDN)));
      return;
    }
    Backend newBackend = DirectoryServer.getBackend(newDN);
    if (newBackend == null)
    {
      setResultCode(ResultCode.NO_SUCH_OBJECT);
      appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get(String
          .valueOf(entryDN), String.valueOf(newDN)));
      return;
    }
    else if (!currentBackend.equals(newBackend))
    {
      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
      appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get(String
          .valueOf(entryDN), String.valueOf(newDN)));
      return;
    }
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Acquire write locks for the current and new DN.
    final Lock currentLock = LockManager.lockWrite(entryDN);
    if (currentLock == null)
    {
      setResultCode(ResultCode.BUSY);
      appendErrorMessage(ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get(String
          .valueOf(entryDN)));
      return;
    }
    Lock newLock = null;
    try
    {
      newLock = LockManager.lockWrite(newDN);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      LockManager.unlock(entryDN, currentLock);
      if (newLock != null)
      {
        LockManager.unlock(newDN, newLock);
      }
      setResultCode(DirectoryServer.getServerErrorResultCode());
      appendErrorMessage(ERR_MODDN_EXCEPTION_LOCKING_NEW_DN.get(String
          .valueOf(entryDN), String.valueOf(newDN), getExceptionMessage(e)));
      return;
    }
    if (newLock == null)
    {
      LockManager.unlock(entryDN, currentLock);
      setResultCode(ResultCode.BUSY);
      appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(String
          .valueOf(entryDN), String.valueOf(newDN)));
      return;
    }
    try
    {
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Get the current entry from the appropriate backend. If it doesn't
      // exist, then fail.
      currentEntry = currentBackend.getEntry(entryDN);
      if (getOriginalEntry() == null)
      {
        // See if one of the entry's ancestors exists.
        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();
        }
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get(String
            .valueOf(entryDN)));
        return;
      }
      // Check to see if there are any controls in the request. If so, then
      // see if there is any special processing required.
      handleRequestControls();
      // Check to see if the client has permission to perform the
      // modify DN.
      // 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 or new superior
      // already exists may have already exposed sensitive information
      // to the client.
      try
      {
        if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
            .isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
              .get(String.valueOf(entryDN)));
          return;
        }
      }
      catch (DirectoryException e)
      {
        setResultCode(e.getResultCode());
        appendErrorMessage(e.getMessageObject());
        return;
      }
      // Duplicate the entry and set its new DN. Also, create an empty list
      // to hold the attribute-level modifications.
      newEntry = currentEntry.duplicate(false);
      newEntry.setDN(newDN);
      // init the modifications
      addModification(null);
      List<Modification> modifications = this.getModifications();
      if (!handleConflictResolution())
      {
        return;
      }
      // If the operation is not a synchronization operation,
      // - Apply the RDN changes.
      // - Invoke the pre-operation modify DN plugins.
      // - apply additional modifications provided by the plugins.
      // If the operation is a synchronization operation
      // - apply the operation as it was originally done on the master.
      if (!isSynchronizationOperation())
      {
        // Apply any changes to the entry based on the change in its RDN.
        // Also perform schema checking on the updated entry.
        applyRDNChanges(modifications);
        // Check for a request to cancel this operation.
        checkIfCanceled(false);
        // Get a count of the current number of modifications. The
        // pre-operation plugins may alter this list, and we need to be able
        // to identify which changes were made after they're done.
        int modCount = modifications.size();
        executePostOpPlugins.value = true;
        PluginResult.PreOperation preOpResult =
            DirectoryServer.getPluginConfigManager()
                .invokePreOperationModifyDNPlugins(this);
        if (!preOpResult.continueProcessing())
        {
          setResultCode(preOpResult.getResultCode());
          appendErrorMessage(preOpResult.getErrorMessage());
          setMatchedDN(preOpResult.getMatchedDN());
          setReferralURLs(preOpResult.getReferralURLs());
          return;
        }
        // Check to see if any of the pre-operation plugins made any changes
        // to the entry. If so, then apply them.
        if (modifications.size() > modCount)
        {
          applyPreOpModifications(modifications, modCount, true);
        }
      }
      else
      {
        // This is a synchronization operation
        // Apply the modifications as they were originally done.
        applyRDNChanges(modifications);
        applyPreOpModifications(modifications, 0, false);
      }
      // Actually perform the modify DN operation.
      // This should include taking
      // care of any synchronization that might be needed.
      // If it is not a private backend, then check to see if the server or
      // backend is operating in read-only mode.
      if (!currentBackend.isPrivateBackend())
      {
        switch (DirectoryServer.getWritabilityMode())
        {
        case DISABLED:
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(String
              .valueOf(entryDN)));
          return;
        case INTERNAL_ONLY:
          if (!(isInternalOperation() || isSynchronizationOperation()))
          {
            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(String
                .valueOf(entryDN)));
            return;
          }
        }
        switch (currentBackend.getWritabilityMode())
        {
        case DISABLED:
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(String
              .valueOf(entryDN)));
          return;
        case INTERNAL_ONLY:
          if (!(isInternalOperation() || isSynchronizationOperation()))
          {
            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(String
                .valueOf(entryDN)));
            return;
          }
        }
      }
      if (noOp)
      {
        appendErrorMessage(INFO_MODDN_NOOP.get());
        setResultCode(ResultCode.NO_OPERATION);
      }
      else
      {
        if (!processPreOperation())
        {
          return;
        }
        currentBackend.renameEntry(entryDN, newEntry, this);
      }
      // Attach the pre-read and/or post-read controls to the response if
      // appropriate.
      LocalBackendWorkflowElement.addPreReadResponse(this, preReadRequest,
          currentEntry);
      LocalBackendWorkflowElement.addPostReadResponse(this, postReadRequest,
          newEntry);
      if (!noOp)
      {
        setResultCode(ResultCode.SUCCESS);
      }
    }
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      setResponseData(de);
      return;
    }
    finally
    {
      LockManager.unlock(entryDN, currentLock);
      LockManager.unlock(newDN, newLock);
      processSynchPostOperationPlugins();
    }
  }
  /**
   * Processes the set of controls included in the request.
@@ -684,8 +600,7 @@
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify DN operation to fail.
   */
  protected void handleRequestControls()
          throws DirectoryException
  private void handleRequestControls() throws DirectoryException
  {
    List<Control> requestControls = getRequestControls();
    if ((requestControls != null) && (! requestControls.isEmpty()))
@@ -790,7 +705,7 @@
          addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
              "obsoleteProxiedAuthzV1Control"));
          // The requester must have the PROXIED_AUTH privilige in order to
          // The requester must have the PROXIED_AUTH privilege in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
@@ -814,7 +729,7 @@
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to
          // The requester must have the PROXIED_AUTH privilege in order to
          // be able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
@@ -864,7 +779,7 @@
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify DN operation to fail.
   */
  protected void applyRDNChanges(List<Modification> modifications)
  private void applyRDNChanges(List<Modification> modifications)
          throws DirectoryException
  {
    // If we should delete the old RDN values from the entry, then do so.
@@ -891,8 +806,7 @@
          }
        }
        LinkedList<AttributeValue> missingValues =
             new LinkedList<AttributeValue>();
        List<AttributeValue> missingValues = new LinkedList<AttributeValue>();
        newEntry.removeAttribute(a, missingValues);
        if (missingValues.isEmpty())
@@ -912,8 +826,7 @@
          newRDN.getAttributeName(i),
          newRDN.getAttributeValue(i));
      LinkedList<AttributeValue> duplicateValues =
           new LinkedList<AttributeValue>();
      List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>();
      newEntry.addAttribute(a, duplicateValues);
      if (duplicateValues.isEmpty())
@@ -981,7 +894,7 @@
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify DN operation to fail.
   */
  protected void applyPreOpModifications(List<Modification> modifications,
  private void applyPreOpModifications(List<Modification> modifications,
                                       int startPos, boolean checkSchema)
          throws DirectoryException
  {
@@ -993,14 +906,13 @@
      switch (m.getModificationType())
      {
        case ADD:
          LinkedList<AttributeValue> duplicateValues =
          List<AttributeValue> duplicateValues =
               new LinkedList<AttributeValue>();
          newEntry.addAttribute(a, duplicateValues);
          break;
        case DELETE:
          LinkedList<AttributeValue> missingValues =
               new LinkedList<AttributeValue>();
          List<AttributeValue> missingValues = new LinkedList<AttributeValue>();
          newEntry.removeAttribute(a, missingValues);
          break;
@@ -1038,7 +950,8 @@
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   */
  protected boolean handleConflictResolution() {
  private boolean handleConflictResolution()
  {
      boolean returnVal = true;
      for (SynchronizationProvider<?> provider :
@@ -1075,7 +988,8 @@
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   */
  protected boolean processPreOperation() {
  private boolean processPreOperation()
  {
      boolean returnVal = true;
      for (SynchronizationProvider<?> provider :
@@ -1108,7 +1022,8 @@
  /**
   * Invoke post operation synchronization providers.
   */
  protected void processSynchPostOperationPlugins() {
  private void processSynchPostOperationPlugins()
  {
      for (SynchronizationProvider<?> provider : DirectoryServer
              .getSynchronizationProviders()) {
          try {
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
@@ -27,8 +27,6 @@
 */
package org.opends.server.workflowelement.localbackend;
import java.util.List;
import org.opends.server.api.Backend;
@@ -43,7 +41,6 @@
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.core.SearchOperationWrapper;
import org.opends.server.core.SearchOperation;
import org.opends.server.loggers.debug.DebugTracer;
@@ -58,8 +55,6 @@
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an operation used to search for entries in a local backend
 * of the Directory Server.
@@ -79,33 +74,33 @@
  /**
   * The backend in which the search is to be performed.
   */
  protected Backend backend;
  private Backend backend;
  /**
   * Indicates whether we should actually process the search.  This should
   * only be false if it's a persistent search with changesOnly=true.
   */
  protected boolean processSearch;
  private boolean processSearch;
  /**
   * The client connection for the search operation.
   */
  protected ClientConnection clientConnection;
  private ClientConnection clientConnection;
  /**
   * The base DN for the search.
   */
  protected DN baseDN;
  private DN baseDN;
  /**
   * The persistent search request, if applicable.
   */
  protected PersistentSearch persistentSearch;
  private PersistentSearch persistentSearch;
  /**
   * The filter for the search.
   */
  protected SearchFilter filter;
  private SearchFilter filter;
@@ -134,195 +129,27 @@
  public void processLocalSearch(LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
    this.backend = wfe.getBackend();
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
      DirectoryServer.getPluginConfigManager();
    processSearch = true;
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
searchProcessing:
    {
      // Process the search base and filter to convert them from their raw forms
      // as provided by the client to the forms required for the rest of the
      // search processing.
      baseDN = getBaseDN();
      filter = getFilter();
      if ((baseDN == null) || (filter == null)){
        break searchProcessing;
      }
      // Check to see if there are any controls in the request.  If so, then
      // see if there is any special processing required.
      try
      {
        handleRequestControls();
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResponseData(de);
        break searchProcessing;
      }
      // Check to see if the client has permission to perform the
      // search.
      // FIXME: for now assume that this will check all permission
      // pertinent to the operation. This includes proxy authorization
      // and any other controls specified.
      try
      {
        if (!AccessControlConfigManager.getInstance()
            .getAccessControlHandler().isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
              .get(String.valueOf(baseDN)));
          break searchProcessing;
        }
      }
      catch (DirectoryException e)
      {
        setResultCode(e.getResultCode());
        appendErrorMessage(e.getMessageObject());
        break searchProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Invoke the pre-operation search plugins.
      executePostOpPlugins = true;
      PluginResult.PreOperation preOpResult =
          pluginConfigManager.invokePreOperationSearchPlugins(this);
      if (!preOpResult.continueProcessing())
      {
        setResultCode(preOpResult.getResultCode());
        appendErrorMessage(preOpResult.getErrorMessage());
        setMatchedDN(preOpResult.getMatchedDN());
        setReferralURLs(preOpResult.getReferralURLs());
        break searchProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Get the backend that should hold the search base.  If there is none,
      // then fail.
      if (backend == null)
      {
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(
                                String.valueOf(baseDN)));
        break searchProcessing;
      }
      // We'll set the result code to "success".  If a problem occurs, then it
      // will be overwritten.
      setResultCode(ResultCode.SUCCESS);
      // If there's a persistent search, then register it with the server.
      if (persistentSearch != null)
      {
        //The Core server maintains the count of concurrent persistent searches
        //so that all the backends (Remote and Local)  are aware of it. Verify
        //with the core if we have already reached the threshold.
        if(!DirectoryServer.allowNewPersistentSearch())
        {
          setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED);
          appendErrorMessage(ERR_MAX_PSEARCH_LIMIT_EXCEEDED.get());
          break searchProcessing;
        }
        wfe.registerPersistentSearch(persistentSearch);
        persistentSearch.enable();
      }
      // Process the search in the backend and all its subordinates.
      try
      {
        if (processSearch)
        {
          backend.search(this);
        }
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.VERBOSE, de);
        }
        setResponseData(de);
        if (persistentSearch != null)
        {
          persistentSearch.cancel();
          setSendResponse(true);
        }
        break searchProcessing;
      }
      catch (CanceledOperationException coe)
      {
        if (persistentSearch != null)
        {
          persistentSearch.cancel();
          setSendResponse(true);
        }
        throw coe;
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION.get(
                                getExceptionMessage(e)));
        if (persistentSearch != null)
        {
          persistentSearch.cancel();
          setSendResponse(true);
        }
        break searchProcessing;
      }
    }
    BooleanHolder executePostOpPlugins = new BooleanHolder();
    processSearch(wfe, executePostOpPlugins);
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Invoke the post-operation search plugins.
    if (executePostOpPlugins)
    if (executePostOpPlugins.value)
    {
      PluginResult.PostOperation postOpResult =
           pluginConfigManager.invokePostOperationSearchPlugins(this);
          DirectoryServer.getPluginConfigManager()
              .invokePostOperationSearchPlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
@@ -333,15 +160,180 @@
    }
  }
  private void processSearch(LocalBackendWorkflowElement wfe,
      BooleanHolder executePostOpPlugins) throws CanceledOperationException
  {
    // Process the search base and filter to convert them from their raw forms
    // as provided by the client to the forms required for the rest of the
    // search processing.
    baseDN = getBaseDN();
    filter = getFilter();
    if ((baseDN == null) || (filter == null))
    {
      return;
    }
    // Check to see if there are any controls in the request. If so, then
    // see if there is any special processing required.
    try
    {
      handleRequestControls();
    }
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      setResponseData(de);
      return;
    }
    // Check to see if the client has permission to perform the
    // search.
    // FIXME: for now assume that this will check all permission
    // pertinent to the operation. This includes proxy authorization
    // and any other controls specified.
    try
    {
      if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
          .isAllowed(this))
      {
        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        appendErrorMessage(ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
            .get(String.valueOf(baseDN)));
        return;
      }
    }
    catch (DirectoryException e)
    {
      setResultCode(e.getResultCode());
      appendErrorMessage(e.getMessageObject());
      return;
    }
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Invoke the pre-operation search plugins.
    executePostOpPlugins.value = true;
    PluginResult.PreOperation preOpResult =
        DirectoryServer.getPluginConfigManager()
            .invokePreOperationSearchPlugins(this);
    if (!preOpResult.continueProcessing())
    {
      setResultCode(preOpResult.getResultCode());
      appendErrorMessage(preOpResult.getErrorMessage());
      setMatchedDN(preOpResult.getMatchedDN());
      setReferralURLs(preOpResult.getReferralURLs());
      return;
    }
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Get the backend that should hold the search base. If there is none,
    // then fail.
    if (backend == null)
    {
      setResultCode(ResultCode.NO_SUCH_OBJECT);
      appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(String
          .valueOf(baseDN)));
      return;
    }
    // We'll set the result code to "success". If a problem occurs, then it
    // will be overwritten.
    setResultCode(ResultCode.SUCCESS);
    // If there's a persistent search, then register it with the server.
    if (persistentSearch != null)
    {
      // The Core server maintains the count of concurrent persistent searches
      // so that all the backends (Remote and Local) are aware of it. Verify
      // with the core if we have already reached the threshold.
      if (!DirectoryServer.allowNewPersistentSearch())
      {
        setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED);
        appendErrorMessage(ERR_MAX_PSEARCH_LIMIT_EXCEEDED.get());
        return;
      }
      wfe.registerPersistentSearch(persistentSearch);
      persistentSearch.enable();
    }
    // Process the search in the backend and all its subordinates.
    try
    {
      if (processSearch)
      {
        backend.search(this);
      }
    }
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.VERBOSE, de);
      }
      setResponseData(de);
      if (persistentSearch != null)
      {
        persistentSearch.cancel();
        setSendResponse(true);
      }
      return;
    }
    catch (CanceledOperationException coe)
    {
      if (persistentSearch != null)
      {
        persistentSearch.cancel();
        setSendResponse(true);
      }
      throw coe;
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      setResultCode(DirectoryServer.getServerErrorResultCode());
      appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION
          .get(getExceptionMessage(e)));
      if (persistentSearch != null)
      {
        persistentSearch.cancel();
        setSendResponse(true);
      }
    }
  }
  /**
   * Handles any controls contained in the request.
   *
   * @throws  DirectoryException  If there is a problem with any of the request
   *                              controls.
   * @throws DirectoryException
   *           If there is a problem with any of the request controls.
   */
  protected void handleRequestControls()
          throws DirectoryException
  private void handleRequestControls() throws DirectoryException
  {
    List<Control> requestControls  = getRequestControls();
    if ((requestControls != null) && (! requestControls.isEmpty()))
@@ -443,7 +435,7 @@
          addAdditionalLogItem(AdditionalLogItem.keyOnly(getClass(),
              "obsoleteProxiedAuthzV1Control"));
          // The requester must have the PROXIED_AUTH privilige in order to be
          // The requester must have the PROXIED_AUTH privilege in order to be
          // able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
@@ -467,7 +459,7 @@
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to be
          // The requester must have the PROXIED_AUTH privilege in order to be
          // able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {