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

neil_a_wilson
25.15.2007 b1e3b0ccdaa423b68ef6fa2fee67d3e09990985f
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -26,108 +26,24 @@
 */
package org.opends.server.workflowelement.localbackend;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.messages.CoreMessages.*;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import static org.opends.server.util.StaticUtils.secondsToTimeString;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
import org.opends.server.api.AttributeSyntax;
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.SASLMechanismHandler;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.api.plugin.PostOperationPluginResult;
import org.opends.server.api.plugin.PreOperationPluginResult;
import org.opends.server.controls.AuthorizationIdentityResponseControl;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.LDAPPostReadRequestControl;
import org.opends.server.controls.LDAPPostReadResponseControl;
import org.opends.server.controls.LDAPPreReadRequestControl;
import org.opends.server.controls.LDAPPreReadResponseControl;
import org.opends.server.controls.MatchedValuesControl;
import org.opends.server.controls.PasswordExpiredControl;
import org.opends.server.controls.PasswordExpiringControl;
import org.opends.server.controls.PasswordPolicyErrorType;
import org.opends.server.controls.PasswordPolicyResponseControl;
import org.opends.server.controls.PasswordPolicyWarningType;
import org.opends.server.controls.PersistentSearchControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
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.ModifyDNOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.PasswordPolicy;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.core.SearchOperation;
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.LDAPException;
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.BooleanSyntax;
import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.AcceptRejectWarn;
import org.opends.server.types.AccountStatusNotification;
import org.opends.server.types.AccountStatusNotificationType;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ByteString;
import org.opends.server.types.CancelResult;
import org.opends.server.types.CancelledOperationException;
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.LockManager;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.Operation;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.types.WritabilityMode;
import org.opends.server.util.Validator;
import org.opends.server.workflowelement.LeafWorkflowElement;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.AccessLogger.*;
/**
 * This class defines a local backend workflow element; e-g an entity that
@@ -135,46 +51,36 @@
 */
public class LocalBackendWorkflowElement extends LeafWorkflowElement
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = DebugLogger.getTracer();
  // the backend associated with the local workflow element
  private Backend backend;
  // the set of local backend workflow elements registered with the server
  private static
    TreeMap<String, LocalBackendWorkflowElement> registeredLocalBackends =
      new TreeMap<String, LocalBackendWorkflowElement>();
  private static TreeMap<String, LocalBackendWorkflowElement>
       registeredLocalBackends =
            new TreeMap<String, LocalBackendWorkflowElement>();
  // a lock to guarantee safe concurrent access to the registeredLocalBackends
  // variable
  private static Object registeredLocalBackendsLock = new Object();
  /**
   * Creates a new instance of the local backend workflow element.
   *
   * @param workflowElementID  the workflow element identifier
   * @param backend  the backend associated to that workflow element
   */
  private LocalBackendWorkflowElement(
      String  workflowElementID,
      Backend backend
      )
  private LocalBackendWorkflowElement(String workflowElementID, Backend backend)
  {
    super(workflowElementID);
    this.backend  = backend;
    if (this.backend != null)
    {
      isPrivate = this.backend.isPrivateBackend();
    }
    setPrivate(backend.isPrivateBackend());
  }
  /**
   * Creates and registers a local backend with the server.
   *
@@ -186,10 +92,8 @@
   *         already created or a newly created local backend workflow
   *         element.
   */
  public static LocalBackendWorkflowElement create(
      String  workflowElementID,
      Backend backend
      )
  public static LocalBackendWorkflowElement create(String workflowElementID,
                                                   Backend backend)
  {
    LocalBackendWorkflowElement localBackend = null;
@@ -197,8 +101,8 @@
    localBackend = registeredLocalBackends.get(workflowElementID);
    if (localBackend == null)
    {
      localBackend = new LocalBackendWorkflowElement(
          workflowElementID, backend);
      localBackend = new LocalBackendWorkflowElement(workflowElementID,
                                                     backend);
      // store the new local backend in the list of registered backends
      registerLocalBackend(localBackend);
@@ -208,19 +112,19 @@
  }
  /**
   * Removes a local backend that was registered with the server.
   *
   * @param workflowElementID  the identifier of the workflow element to remove
   */
  public static void remove(
      String workflowElementID
      )
  public static void remove(String workflowElementID)
  {
    deregisterLocalBackend(workflowElementID);
  }
  /**
   * Removes all the local backends that were registered with the server.
   * This function is intended to be called when the server is shutting down.
@@ -238,14 +142,14 @@
  }
  /**
   * Registers a local backend with the server.
   *
   * @param localBackend  the local backend to register with the server
   */
  private static void registerLocalBackend(
      LocalBackendWorkflowElement localBackend
      )
                           LocalBackendWorkflowElement localBackend)
  {
    synchronized (registeredLocalBackendsLock)
    {
@@ -265,14 +169,13 @@
  }
  /**
   * Deregisters a local backend with the server.
   *
   * @param workflowElementID  the identifier of the workflow element to remove
   */
  private static void deregisterLocalBackend(
      String workflowElementID
      )
  private static void deregisterLocalBackend(String workflowElementID)
  {
    synchronized (registeredLocalBackendsLock)
    {
@@ -282,8 +185,8 @@
      if (existingLocalBackend != null)
      {
        TreeMap<String, LocalBackendWorkflowElement> newLocalBackends =
          new TreeMap
            <String, LocalBackendWorkflowElement>(registeredLocalBackends);
             new TreeMap<String, LocalBackendWorkflowElement>(
                      registeredLocalBackends);
        newLocalBackends.remove(workflowElementID);
        registeredLocalBackends = newLocalBackends;
      }
@@ -291,6 +194,7 @@
  }
  /**
   * {@inheritDoc}
   */
@@ -298,8432 +202,60 @@
  {
    switch (operation.getOperationType())
    {
    case BIND:
      processBind((BindOperation) operation);
      break;
    case SEARCH:
      processSearch((SearchOperation) operation);
      break;
    case ADD:
      processAdd((AddOperation) operation);
      break;
    case DELETE:
      processDelete((DeleteOperation) operation);
      break;
    case MODIFY:
      processModify((ModifyOperation) operation);
      break;
    case MODIFY_DN:
      processModifyDN((ModifyDNOperation) operation);
      break;
    case COMPARE:
      processCompare((CompareOperation) operation);
      break;
    case ABANDON:
      // There is no processing for an abandon operation.
      break;
    default:
      // jdemendi - temporary code, just make sure that we are not falling
      // into that incomplete code...
      Validator.ensureTrue(false);
      break;
      case BIND:
        LocalBackendBindOperation bindOperation =
             new LocalBackendBindOperation((BindOperation) operation);
        bindOperation.processLocalBind(backend);
        break;
      case SEARCH:
        LocalBackendSearchOperation searchOperation =
             new LocalBackendSearchOperation((SearchOperation) operation);
        searchOperation.processLocalSearch(backend);
        break;
      case ADD:
        LocalBackendAddOperation addOperation =
             new LocalBackendAddOperation((AddOperation) operation);
        addOperation.processLocalAdd(backend);
        break;
      case DELETE:
        LocalBackendDeleteOperation deleteOperation =
             new LocalBackendDeleteOperation((DeleteOperation) operation);
        deleteOperation.processLocalDelete(backend);
        break;
      case MODIFY:
        LocalBackendModifyOperation modifyOperation =
             new LocalBackendModifyOperation((ModifyOperation) operation);
        modifyOperation.processLocalModify(backend);
        break;
      case MODIFY_DN:
        LocalBackendModifyDNOperation modifyDNOperation =
             new LocalBackendModifyDNOperation((ModifyDNOperation) operation);
        modifyDNOperation.processLocalModifyDN(backend);
        break;
      case COMPARE:
        LocalBackendCompareOperation compareOperation =
             new LocalBackendCompareOperation((CompareOperation) operation);
        compareOperation.processLocalCompare(backend);
        break;
      case ABANDON:
        // There is no processing for an abandon operation.
        break;
      default:
        throw new AssertionError("Attempted to execute an invalid operation " +
                                 "type:  " + operation.getOperationType() +
                                 " (" + operation + ")");
    }
  }
  /**
   * Perform a modify operation against a local backend.
   *
   * @param operation - The operation to perform
   */
  public void processModify(ModifyOperation operation)
  {
    LocalBackendModifyOperation localOperation =
      new LocalBackendModifyOperation(operation);
    processLocalModify(localOperation);
  }
  /**
   * Perform a local modify operation against the local backend.
   *
   * @param localOp - The operation to perform
   */
  private void processLocalModify(LocalBackendModifyOperation localOp)
  {
    ClientConnection clientConnection = localOp.getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
      DirectoryServer.getPluginConfigManager();
    boolean skipPostOperation = false;
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
    boolean                 pwPolicyControlRequested = false;
    PasswordPolicyErrorType pwpErrorType             = null;
    modifyProcessing:
    {
      DN entryDN = localOp.getEntryDN();
      if (entryDN == null){
        break modifyProcessing;
      }
      // Process the modifications to convert them from their raw form to the
      // form required for the rest of the modify processing.
      List<Modification> modifications = localOp.getModifications();
      if (modifications == null)
      {
        break modifyProcessing;
      }
      if (modifications.isEmpty())
      {
        localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
        localOp.appendErrorMessage(
            ERR_MODIFY_NO_MODIFICATIONS.get(String.valueOf(entryDN)));
        break modifyProcessing;
      }
      // If the user must change their password before doing anything else, and
      // if the target of the modify operation isn't the user's own entry, then
      // reject the request.
      if ((! localOp.isInternalOperation()) &&
          clientConnection.mustChangePassword())
      {
        DN authzDN = localOp.getAuthorizationDN();
        if ((authzDN != null) && (! authzDN.equals(entryDN)))
        {
          // The user will not be allowed to do anything else before the
          // password gets changed.  Also note that we haven't yet checked the
          // request controls so we need to do that now to see if the password
          // policy request control was provided.
          for (Control c : localOp.getRequestControls())
          {
            if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
            {
              pwPolicyControlRequested = true;
              pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
              break;
            }
          }
          localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          localOp.appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
          break modifyProcessing;
        }
      }
      // Check for a request to cancel this operation.
      if (localOp.getCancelRequest() != null)
      {
        return;
      }
      // Acquire a write lock on the target entry.
      Lock entryLock = null;
      for (int i=0; i < 3; i++)
      {
        entryLock = LockManager.lockWrite(entryDN);
        if (entryLock != null)
        {
          break;
        }
      }
      if (entryLock == null)
      {
        localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
        localOp.appendErrorMessage(
            ERR_MODIFY_CANNOT_LOCK_ENTRY.get(String.valueOf(entryDN)));
        skipPostOperation = true;
        break modifyProcessing;
      }
      try
      {
        // Check for a request to cancel this operation.
        if (localOp.getCancelRequest() != null)
        {
          return;
        }
        // Get the entry to modify.  If it does not exist, then fail.
        Entry currentEntry = null;
        try
        {
          currentEntry = backend.getEntry(entryDN);
          localOp.setCurrentEntry(currentEntry);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          localOp.setResponseData(de);
          break modifyProcessing;
        }
        if (currentEntry == null)
        {
          localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
          localOp.appendErrorMessage(ERR_MODIFY_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))
              {
                localOp.setMatchedDN(parentDN);
                break;
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              break;
            }
            parentDN = parentDN.getParentDNInSuffix();
          }
          break modifyProcessing;
        }
        // Check to see if there are any controls in the request.  If so, then
        // see if there is any special processing required.
        boolean                    noOp            = false;
        LDAPPreReadRequestControl  preReadRequest  = null;
        LDAPPostReadRequestControl postReadRequest = null;
        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 (!AccessControlConfigManager.getInstance().
                     getAccessControlHandler().
                     isAllowed(entryDN, localOp, c))
            {
              localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
              localOp.appendErrorMessage(
                      ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
              skipPostOperation = true;
              break modifyProcessing;
            }
            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.getMessageObject());
                  break modifyProcessing;
                }
              }
              try
              {
                // FIXME -- We need to determine whether the current user has
                //          permission to make this determination.
                SearchFilter filter = assertControl.getSearchFilter();
                if (! filter.matchesEntry(currentEntry))
                {
                  localOp.setResultCode(ResultCode.ASSERTION_FAILED);
                  localOp.appendErrorMessage(
                      ERR_MODIFY_ASSERTION_FAILED.get(String.valueOf(entryDN)));
                  break modifyProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
                localOp.appendErrorMessage(
                        ERR_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
                break modifyProcessing;
              }
            }
            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
            {
              noOp = true;
            }
            else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
            {
              if (c instanceof LDAPPreReadRequestControl)
              {
                preReadRequest = (LDAPPreReadRequestControl) c;
              }
              else
              {
                try
                {
                  preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
                  requestControls.set(i, preReadRequest);
                }
                catch (LDAPException le)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
                  }
                  localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
                  localOp.appendErrorMessage(le.getMessageObject());
                  break modifyProcessing;
                }
              }
            }
            else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
            {
              if (c instanceof LDAPPostReadRequestControl)
              {
                postReadRequest = (LDAPPostReadRequestControl) c;
              }
              else
              {
                try
                {
                  postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
                  requestControls.set(i, postReadRequest);
                }
                catch (LDAPException le)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
                  }
                  localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
                  localOp.appendErrorMessage(le.getMessageObject());
                  break modifyProcessing;
                }
              }
            }
            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))
              {
                localOp.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
                break modifyProcessing;
              }
              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.getMessageObject());
                  break modifyProcessing;
                }
              }
              Entry authorizationEntry;
              try
              {
                authorizationEntry = proxyControl.getAuthorizationEntry();
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(de.getResultCode());
                localOp.appendErrorMessage(de.getMessageObject());
                break modifyProcessing;
              }
              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))
              {
                localOp.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
                break modifyProcessing;
              }
              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.getMessageObject());
                  break modifyProcessing;
                }
              }
              Entry authorizationEntry;
              try
              {
                authorizationEntry = proxyControl.getAuthorizationEntry();
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(de.getResultCode());
                localOp.appendErrorMessage(de.getMessageObject());
                break modifyProcessing;
              }
              localOp.setAuthorizationEntry(authorizationEntry);
              if (authorizationEntry == null)
              {
                localOp.setProxiedAuthorizationDN(DN.nullDN());
              }
              else
              {
                localOp.setProxiedAuthorizationDN(authorizationEntry.getDN());
              }
            }
            else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
            {
              pwPolicyControlRequested = true;
            }
            // NYI -- Add support for additional controls.
            else if (c.isCritical())
            {
              if ((backend == null) || (! backend.supportsControl(oid)))
              {
                localOp.setResultCode(
                    ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
                localOp.appendErrorMessage(
                    ERR_MODIFY_UNSUPPORTED_CRITICAL_CONTROL.get(
                            String.valueOf(entryDN),
                            oid));
                break modifyProcessing;
              }
            }
          }
        }
        // Get the password policy state object for the entry that can be used
        // to perform any appropriate password policy processing.  Also, see if
        // the entry is being updated by the end user or an administrator.
        PasswordPolicyState pwPolicyState;
        boolean selfChange = entryDN.equals(localOp.getAuthorizationDN());
        try
        {
          // FIXME -- Need a way to enable debug mode.
          pwPolicyState = new PasswordPolicyState(currentEntry, false, false);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          localOp.setResultCode(de.getResultCode());
          localOp.appendErrorMessage(de.getMessageObject());
          break modifyProcessing;
        }
        // Create a duplicate of the entry and apply the changes to it.
        Entry modifiedEntry = currentEntry.duplicate(false);
        localOp.setModifiedEntry(modifiedEntry);
        if (! noOp)
        {
          // Invoke any conflict resolution processing that might be needed by
          // the synchronization provider.
          for (SynchronizationProvider provider :
            DirectoryServer.getSynchronizationProviders())
          {
            try
            {
              SynchronizationProviderResult result =
                provider.handleConflictResolution(localOp);
              if (! result.continueOperationProcessing())
              {
                break modifyProcessing;
              }
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              logError(ERR_MODIFY_SYNCH_CONFLICT_RESOLUTION_FAILED.
                  get(localOp.getConnectionID(), localOp.getOperationID(),
                      getExceptionMessage(de)));
              localOp.setResponseData(de);
              break modifyProcessing;
            }
          }
        }
        // Declare variables used for password policy state processing.
        boolean passwordChanged = false;
        boolean currentPasswordProvided = false;
        boolean isEnabled = true;
        boolean enabledStateChanged = false;
        int numPasswords;
        if (currentEntry.hasAttribute(
            pwPolicyState.getPolicy().getPasswordAttribute()))
        {
          // It may actually have more than one, but we can't tell the
          // difference if the values are encoded, and its enough for our
          // purposes just to know that there is at least one.
          numPasswords = 1;
        }
        else
        {
          numPasswords = 0;
        }
        // If it's not an internal or synchronization operation, then iterate
        // through the set of modifications to see if a password is included in
        // the changes.  If so, then add the appropriate state changes to the
        // set of modifications.
        if (! (localOp.isInternalOperation()
            || localOp.isSynchronizationOperation()))
        {
          for (Modification m : modifications)
          {
            if (m.getAttribute().getAttributeType().equals(
                pwPolicyState.getPolicy().getPasswordAttribute()))
            {
              passwordChanged = true;
              if (! selfChange)
              {
                if (! clientConnection.hasPrivilege(
                    Privilege.PASSWORD_RESET,
                    localOp))
                {
                  pwpErrorType =
                       PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
                  localOp.appendErrorMessage(
                          ERR_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES.get());
                  localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
                  break modifyProcessing;
                }
              }
              break;
            }
          }
        }
        for (Modification m : modifications)
        {
          Attribute     a = m.getAttribute();
          AttributeType t = a.getAttributeType();
          // If the attribute type is marked "NO-USER-MODIFICATION" then fail
          // unless this is an internal operation or is related to
          // synchronization in some way.
          if (t.isNoUserModification())
          {
            if (! (localOp.isInternalOperation() ||
                localOp.isSynchronizationOperation() ||
                m.isInternal()))
            {
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              localOp.appendErrorMessage(
                  ERR_MODIFY_ATTR_IS_NO_USER_MOD.get(String.valueOf(entryDN),
                  a.getName()));
              break modifyProcessing;
            }
          }
          // If the attribute type is marked "OBSOLETE" and the modification
          // is setting new values, then fail unless this is an internal
          // operation or is related to synchronization in some way.
          if (t.isObsolete())
          {
            if (a.hasValue() &&
                (m.getModificationType() != ModificationType.DELETE))
            {
              if (! (localOp.isInternalOperation() ||
                  localOp.isSynchronizationOperation() ||
                  m.isInternal()))
              {
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                    ERR_MODIFY_ATTR_IS_OBSOLETE.get(String.valueOf(entryDN),
                    a.getName()));
                break modifyProcessing;
              }
            }
          }
          // See if the attribute is one which controls the privileges available
          // for a user.  If it is, then the client must have the
          // PRIVILEGE_CHANGE privilege.
          if (t.hasName(OP_ATTR_PRIVILEGE_NAME))
          {
            if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE,
                localOp))
            {
              localOp.appendErrorMessage(
                      ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES
                              .get());
              localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
              break modifyProcessing;
            }
          }
          // If the modification is updating the password attribute, then
          // perform any necessary password policy processing.  This processing
          // should be skipped for synchronization operations.
          boolean isPassword
          = t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
          if (isPassword && (!(localOp.isSynchronizationOperation())))
          {
            // If the attribute contains any options, then reject it.  Passwords
            // will not be allowed to have options. Skipped for internal
            // operations.
            if(!localOp.isInternalOperation())
            {
              if (a.hasOptions())
              {
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                        ERR_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS.get());
                break modifyProcessing;
              }
              // If it's a self change, then see if that's allowed.
              if (selfChange &&
                  (! pwPolicyState.getPolicy().allowUserPasswordChanges()))
              {
                pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                        ERR_MODIFY_NO_USER_PW_CHANGES.get());
                break modifyProcessing;
              }
              // If we require secure password changes, then makes sure it's a
              // secure communication channel.
              if (pwPolicyState.getPolicy().requireSecurePasswordChanges() &&
                  (! clientConnection.isSecure()))
              {
                pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                        ERR_MODIFY_REQUIRE_SECURE_CHANGES.get());
                break modifyProcessing;
              }
              // If it's a self change and it's not been long enough since the
              // previous change, then reject it.
              if (selfChange && pwPolicyState.isWithinMinimumAge())
              {
                pwpErrorType = PasswordPolicyErrorType.PASSWORD_TOO_YOUNG;
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(ERR_MODIFY_WITHIN_MINIMUM_AGE.get());
                break modifyProcessing;
              }
            }
            // Check to see whether this will adding, deleting, or replacing
            // password values (increment doesn't make any sense for passwords).
            // Then perform the appropriate type of processing for that kind of
            // modification.
            boolean isAdd = false;
            LinkedHashSet<AttributeValue> pwValues = a.getValues();
            LinkedHashSet<AttributeValue> encodedValues =
              new LinkedHashSet<AttributeValue>();
            switch (m.getModificationType())
            {
            case ADD:
            case REPLACE:
              int passwordsToAdd = pwValues.size();
              if (m.getModificationType() == ModificationType.ADD)
              {
                numPasswords += passwordsToAdd;
                isAdd = true;
              }
              else
              {
                numPasswords = passwordsToAdd;
              }
              // If there were multiple password values provided, then make
              // sure that's OK.
              if ((! localOp.isInternalOperation()) &&
                  (! pwPolicyState.getPolicy().allowMultiplePasswordValues()) &&
                  (passwordsToAdd > 1))
              {
                pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                        ERR_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED.get());
                break modifyProcessing;
              }
              // Iterate through the password values and see if any of them
              // are pre-encoded.  If so, then check to see if we'll allow it.
              // Otherwise, store the clear-text values for later validation
              // and update the attribute with the encoded values.
              for (AttributeValue v : pwValues)
              {
                if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
                {
                  if ((!localOp.isInternalOperation()) &&
                      ! pwPolicyState.getPolicy().allowPreEncodedPasswords())
                  {
                    pwpErrorType =
                         PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
                    localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                    localOp.appendErrorMessage(
                            ERR_MODIFY_NO_PREENCODED_PASSWORDS.get());
                    break modifyProcessing;
                  }
                  else
                  {
                    encodedValues.add(v);
                  }
                }
                else
                {
                  if (isAdd)
                  {
                    // Make sure that the password value doesn't already
                    // exist.
                    if (pwPolicyState.passwordMatches(v.getValue()))
                    {
                      pwpErrorType =
                           PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
                      localOp.setResultCode(
                          ResultCode.ATTRIBUTE_OR_VALUE_EXISTS);
                      localOp.appendErrorMessage(
                              ERR_MODIFY_PASSWORD_EXISTS.get());
                      break modifyProcessing;
                    }
                  }
                  List<AttributeValue> newPasswords =
                    localOp.getNewPasswords() ;
                  if (newPasswords == null)
                  {
                    newPasswords = new LinkedList<AttributeValue>();
                    localOp.setNewPasswords(newPasswords);
                  }
                  newPasswords.add(v);
                  try
                  {
                    for (ByteString s :
                      pwPolicyState.encodePassword(v.getValue()))
                    {
                      encodedValues.add(new AttributeValue(
                          a.getAttributeType(), s));
                    }
                  }
                  catch (DirectoryException de)
                  {
                    if (debugEnabled())
                    {
                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
                    }
                    localOp.setResultCode(de.getResultCode());
                    localOp.appendErrorMessage(de.getMessageObject());
                    break modifyProcessing;
                  }
                }
              }
              a.setValues(encodedValues);
              break;
            case DELETE:
              // Iterate through the password values and see if any of them
              // are pre-encoded.  We will never allow pre-encoded passwords
              // for user password changes, but we will allow them for
              // administrators.  For each clear-text value, verify that at
              // least one value in the entry matches and replace the
              // clear-text value with the appropriate encoded forms.
              for (AttributeValue v : pwValues)
              {
                if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
                {
                  if ((!localOp.isInternalOperation()) && selfChange)
                  {
                    pwpErrorType =
                         PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
                    localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                    localOp.appendErrorMessage(
                            ERR_MODIFY_NO_PREENCODED_PASSWORDS.get());
                    break modifyProcessing;
                  }
                  else
                  {
                    encodedValues.add(v);
                  }
                }
                else
                {
                  List<Attribute> attrList = currentEntry.getAttribute(t);
                  if ((attrList == null) || (attrList.isEmpty()))
                  {
                    localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                    localOp.appendErrorMessage(
                            ERR_MODIFY_NO_EXISTING_VALUES.get());
                    break modifyProcessing;
                  }
                  boolean found = false;
                  for (Attribute attr : attrList)
                  {
                    for (AttributeValue av : attr.getValues())
                    {
                      if (pwPolicyState.getPolicy().usesAuthPasswordSyntax())
                      {
                        if (AuthPasswordSyntax.isEncoded(av.getValue()))
                        {
                          try
                          {
                            StringBuilder[] compoenents =
                              AuthPasswordSyntax.decodeAuthPassword(
                                  av.getStringValue());
                            PasswordStorageScheme scheme =
                              DirectoryServer.
                              getAuthPasswordStorageScheme(
                                  compoenents[0].toString());
                            if (scheme != null)
                            {
                              if (scheme.authPasswordMatches(
                                  v.getValue(),
                                  compoenents[1].toString(),
                                  compoenents[2].toString()))
                              {
                                encodedValues.add(av);
                                found = true;
                              }
                            }
                          }
                          catch (DirectoryException de)
                          {
                            if (debugEnabled())
                            {
                              TRACER.debugCaught(
                                  DebugLogLevel.ERROR, de);
                            }
                            localOp.setResultCode(de.getResultCode());
                            localOp.appendErrorMessage(
                                ERR_MODIFY_CANNOT_DECODE_PW.get(
                                        de.getMessageObject()));
                            break modifyProcessing;
                          }
                        }
                        else
                        {
                          if (av.equals(v))
                          {
                            encodedValues.add(v);
                            found = true;
                          }
                        }
                      }
                      else
                      {
                        if (UserPasswordSyntax.isEncoded(av.getValue()))
                        {
                          try
                          {
                            String[] compoenents =
                              UserPasswordSyntax.decodeUserPassword(
                                  av.getStringValue());
                            PasswordStorageScheme scheme =
                              DirectoryServer.getPasswordStorageScheme(
                                  toLowerCase(compoenents[0]));
                            if (scheme != null)
                            {
                              if (scheme.passwordMatches(
                                  v.getValue(),
                                  new ASN1OctetString(compoenents[1])))
                              {
                                encodedValues.add(av);
                                found = true;
                              }
                            }
                          }
                          catch (DirectoryException de)
                          {
                            if (debugEnabled())
                            {
                              TRACER.debugCaught(
                                  DebugLogLevel.ERROR, de);
                            }
                            localOp.setResultCode(de.getResultCode());
                            localOp.appendErrorMessage(
                                    ERR_MODIFY_CANNOT_DECODE_PW.get(
                                            de.getMessageObject()));
                            break modifyProcessing;
                          }
                        }
                        else
                        {
                          if (av.equals(v))
                          {
                            encodedValues.add(v);
                            found = true;
                          }
                        }
                      }
                    }
                  }
                  if (found)
                  {
                    List<AttributeValue> currentPasswords =
                      localOp.getCurrentPasswords();
                    if (currentPasswords == null)
                    {
                      currentPasswords = new LinkedList<AttributeValue>();
                      localOp.setCurrentPasswords(currentPasswords);
                    }
                    currentPasswords.add(v);
                    numPasswords--;
                  }
                  else
                  {
                    localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                    localOp.appendErrorMessage(
                            ERR_MODIFY_INVALID_PASSWORD.get());
                    break modifyProcessing;
                  }
                  currentPasswordProvided = true;
                }
              }
              a.setValues(encodedValues);
              break;
            default:
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            localOp.appendErrorMessage(
                    ERR_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD.get(
                            String.valueOf(m.getModificationType()),
                            a.getName()));
            break modifyProcessing;
            }
          }
          else
          {
            // See if it's an attribute used to maintain the account
            // enabled/disabled state.
            AttributeType disabledAttr =
              DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED,
                  true);
            if (t.equals(disabledAttr))
            {
              enabledStateChanged = true;
              for (AttributeValue v : a.getValues())
              {
                try
                {
                  isEnabled = (! BooleanSyntax.decodeBooleanValue(
                      v.getNormalizedValue()));
                }
                catch (DirectoryException de)
                {
                  localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
                  Message message =
                    ERR_MODIFY_INVALID_DISABLED_VALUE.get(
                            OP_ATTR_ACCOUNT_DISABLED,
                            String.valueOf(de.getMessageObject()));
                  localOp.appendErrorMessage(message);
                  break modifyProcessing;
                }
              }
            }
          }
          switch (m.getModificationType())
          {
          case ADD:
            // Make sure that one or more values have been provided for the
            // attribute.
            LinkedHashSet<AttributeValue> newValues = a.getValues();
            if ((newValues == null) || newValues.isEmpty())
            {
              localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
              localOp.appendErrorMessage(ERR_MODIFY_ADD_NO_VALUES.get(
                      String.valueOf(entryDN),
                      a.getName()));
              break modifyProcessing;
            }
            // If the server is configured to check schema and the
            // operation is not a synchronization operation,
            // make sure that all the new values are valid according to the
            // associated syntax.
            if ((DirectoryServer.checkSchema()) &&
                (!localOp.isSynchronizationOperation()) )
            {
              AcceptRejectWarn syntaxPolicy =
                DirectoryServer.getSyntaxEnforcementPolicy();
              AttributeSyntax syntax = t.getSyntax();
              if (syntaxPolicy == AcceptRejectWarn.REJECT)
              {
                MessageBuilder invalidReason =
                  new MessageBuilder();
                for (AttributeValue v : newValues)
                {
                  if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
                  {
                    localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
                    localOp.appendErrorMessage(ERR_MODIFY_ADD_INVALID_SYNTAX
                            .get(String.valueOf(entryDN),
                                 a.getName(),
                                 v.getStringValue(),
                                 invalidReason.toString()));
                    break modifyProcessing;
                  }
                }
              }
              else if (syntaxPolicy == AcceptRejectWarn.WARN)
              {
                MessageBuilder invalidReason = new MessageBuilder();
                for (AttributeValue v : newValues)
                {
                  if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
                  {
                    localOp.setResultCode(
                        ResultCode.INVALID_ATTRIBUTE_SYNTAX);
                    logError(
                          ERR_MODIFY_ADD_INVALID_SYNTAX.
                            get(String.valueOf(entryDN), a.getName(),
                                v.getStringValue(), invalidReason.toString()));
                    invalidReason = new MessageBuilder();
                  }
                }
              }
            }
            // Add the provided attribute or merge an existing attribute with
            // the values of the new attribute.  If there are any duplicates,
            // then fail.
            LinkedList<AttributeValue> duplicateValues =
              new LinkedList<AttributeValue>();
            if (a.getAttributeType().isObjectClassType())
            {
              try
              {
                modifiedEntry.addObjectClasses(newValues);
                break;
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResponseData(de);
                break modifyProcessing;
              }
            }
            else
            {
              modifiedEntry.addAttribute(a, duplicateValues);
              if (duplicateValues.isEmpty())
              {
                break;
              }
              else
              {
                StringBuilder buffer = new StringBuilder();
                Iterator<AttributeValue> iterator =
                  duplicateValues.iterator();
                buffer.append(iterator.next().getStringValue());
                while (iterator.hasNext())
                {
                  buffer.append(", ");
                  buffer.append(iterator.next().getStringValue());
                }
                localOp.setResultCode(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS);
                localOp.appendErrorMessage(
                    ERR_MODIFY_ADD_DUPLICATE_VALUE.get(String.valueOf(entryDN),
                    a.getName(),
                    buffer.toString()));
                break modifyProcessing;
              }
            }
          case DELETE:
            // Remove the specified attribute values or the entire attribute
            // from the value.  If there are any specified values that were
            // not present, then fail.  If the RDN attribute value would be
            // removed, then fail.
            LinkedList<AttributeValue> missingValues =
              new LinkedList<AttributeValue>();
            boolean attrExists =
              modifiedEntry.removeAttribute(a, missingValues);
            if (attrExists)
            {
              if (missingValues.isEmpty())
              {
                RDN rdn = modifiedEntry.getDN().getRDN();
                if ((rdn !=  null) && rdn.hasAttributeType(t) &&
                    (! modifiedEntry.hasValue(t, a.getOptions(),
                        rdn.getAttributeValue(t))))
                {
                  localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
                  localOp.appendErrorMessage(ERR_MODIFY_DELETE_RDN_ATTR.get(
                          String.valueOf(entryDN),
                      a.getName()));
                  break modifyProcessing;
                }
                break;
              }
              else
              {
                StringBuilder buffer = new StringBuilder();
                Iterator<AttributeValue> iterator = missingValues.iterator();
                buffer.append(iterator.next().getStringValue());
                while (iterator.hasNext())
                {
                  buffer.append(", ");
                  buffer.append(iterator.next().getStringValue());
                }
                localOp.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
                localOp.appendErrorMessage(
                    ERR_MODIFY_DELETE_MISSING_VALUES.get(
                            String.valueOf(entryDN),
                    a.getName(),
                    buffer.toString()));
                break modifyProcessing;
              }
            }
            else
            {
              localOp.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
              localOp.appendErrorMessage(
                  ERR_MODIFY_DELETE_NO_SUCH_ATTR.get(String.valueOf(entryDN),
                  a.getName()));
              break modifyProcessing;
            }
          case REPLACE:
            // If it is the objectclass attribute, then treat that separately.
            if (a.getAttributeType().isObjectClassType())
            {
              try
              {
                modifiedEntry.setObjectClasses(a.getValues());
                break;
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResponseData(de);
                break modifyProcessing;
              }
            }
            // If the provided attribute does not have any values, then we
            // will simply remove the attribute from the entry (if it exists).
            if (! a.hasValue())
            {
              modifiedEntry.removeAttribute(t, a.getOptions());
              RDN rdn = modifiedEntry.getDN().getRDN();
              if ((rdn !=  null) && rdn.hasAttributeType(t) &&
                  (! modifiedEntry.hasValue(t, a.getOptions(),
                      rdn.getAttributeValue(t))))
              {
                localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
                localOp.appendErrorMessage(
                    ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN),
                    a.getName()));
                break modifyProcessing;
              }
              break;
            }
            // If the server is configured to check schema and the
            // operation is not a synchronization operation,
            // make sure that all the new values are valid according to the
            // associated syntax.
            newValues = a.getValues();
            if ((DirectoryServer.checkSchema()) &&
                (!localOp.isSynchronizationOperation()) )
            {
              AcceptRejectWarn syntaxPolicy =
                DirectoryServer.getSyntaxEnforcementPolicy();
              AttributeSyntax syntax = t.getSyntax();
              if (syntaxPolicy == AcceptRejectWarn.REJECT)
              {
                MessageBuilder invalidReason = new MessageBuilder();
                for (AttributeValue v : newValues)
                {
                  if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
                  {
                    localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
                    localOp.appendErrorMessage(
                            ERR_MODIFY_REPLACE_INVALID_SYNTAX.get(
                                    String.valueOf(entryDN),
                        a.getName(),
                        v.getStringValue(),
                        invalidReason.toString()));
                    break modifyProcessing;
                  }
                }
              }
              else if (syntaxPolicy == AcceptRejectWarn.WARN)
              {
                MessageBuilder invalidReason = new MessageBuilder();
                for (AttributeValue v : newValues)
                {
                  if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
                  {
                    localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
                    logError(
                          ERR_MODIFY_REPLACE_INVALID_SYNTAX.
                            get(String.valueOf(entryDN), a.getName(),
                                v.getStringValue(), invalidReason.toString()));
                    invalidReason = new MessageBuilder();
                  }
                }
              }
            }
            // If the provided attribute does not have any options, then we
            // will simply use it in place of any existing attribute of the
            // provided type (or add it if it doesn't exist).
            if (! a.hasOptions())
            {
              List<Attribute> attrList = new ArrayList<Attribute>(1);
              attrList.add(a);
              modifiedEntry.putAttribute(t, attrList);
              RDN rdn = modifiedEntry.getDN().getRDN();
              if ((rdn !=  null) && rdn.hasAttributeType(t) &&
                  (! modifiedEntry.hasValue(t, a.getOptions(),
                      rdn.getAttributeValue(t))))
              {
                localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
                localOp.appendErrorMessage(
                    ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN),
                    a.getName()));
                break modifyProcessing;
              }
              break;
            }
            // See if there is an existing attribute of the provided type.  If
            // not, then we'll use the new one.
            List<Attribute> attrList = modifiedEntry.getAttribute(t);
            if ((attrList == null) || attrList.isEmpty())
            {
              attrList = new ArrayList<Attribute>(1);
              attrList.add(a);
              modifiedEntry.putAttribute(t, attrList);
              RDN rdn = modifiedEntry.getDN().getRDN();
              if ((rdn !=  null) && rdn.hasAttributeType(t) &&
                  (! modifiedEntry.hasValue(t, a.getOptions(),
                      rdn.getAttributeValue(t))))
              {
                localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
                localOp.appendErrorMessage(
                    ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN),
                    a.getName()));
                break modifyProcessing;
              }
              break;
            }
            // There must be an existing occurrence of the provided attribute
            // in the entry.  If there is a version with exactly the set of
            // options provided, then replace it.  Otherwise, add a new one.
            boolean found = false;
            for (int i=0; i < attrList.size(); i++)
            {
              if (attrList.get(i).optionsEqual(a.getOptions()))
              {
                attrList.set(i, a);
                found = true;
                break;
              }
            }
            if (! found)
            {
              attrList.add(a);
            }
            RDN rdn = modifiedEntry.getDN().getRDN();
            if ((rdn !=  null) && rdn.hasAttributeType(t) &&
                (! modifiedEntry.hasValue(t, a.getOptions(),
                    rdn.getAttributeValue(t))))
            {
              localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
              localOp.appendErrorMessage(
                  ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN),
                  a.getName()));
              break modifyProcessing;
            }
            break;
          case INCREMENT:
            // The specified attribute type must not be an RDN attribute.
            rdn = modifiedEntry.getDN().getRDN();
            if ((rdn !=  null) && rdn.hasAttributeType(t))
            {
              localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
              localOp.appendErrorMessage(ERR_MODIFY_INCREMENT_RDN.get(
                      String.valueOf(entryDN),
                  a.getName()));
            }
            // The provided attribute must have a single value, and it must be
            // an integer.
            LinkedHashSet<AttributeValue> values = a.getValues();
            if ((values == null) || values.isEmpty())
            {
              localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
              localOp.appendErrorMessage(
                  ERR_MODIFY_INCREMENT_REQUIRES_VALUE.get(
                          String.valueOf(entryDN),
                  a.getName()));
              break modifyProcessing;
            }
            else if (values.size() > 1)
            {
              localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
              localOp.appendErrorMessage(
                  ERR_MODIFY_INCREMENT_REQUIRES_SINGLE_VALUE.get(
                          String.valueOf(entryDN),
                  a.getName()));
              break modifyProcessing;
            }
            AttributeValue v = values.iterator().next();
            long incrementValue;
            try
            {
              incrementValue = Long.parseLong(v.getNormalizedStringValue());
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
              localOp.appendErrorMessage(
                  ERR_MODIFY_INCREMENT_PROVIDED_VALUE_NOT_INTEGER.get(
                          String.valueOf(entryDN),
                  a.getName(), v.getStringValue()));
              break modifyProcessing;
            }
            // Get the corresponding attribute from the entry and make sure
            // that it has a single integer value.
            attrList = modifiedEntry.getAttribute(t, a.getOptions());
            if ((attrList == null) || attrList.isEmpty())
            {
              localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
              localOp.appendErrorMessage(
                  ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get(
                          String.valueOf(entryDN),
                  a.getName()));
              break modifyProcessing;
            }
            boolean updated = false;
            for (Attribute attr : attrList)
            {
              LinkedHashSet<AttributeValue> valueList = attr.getValues();
              if ((valueList == null) || valueList.isEmpty())
              {
                continue;
              }
              LinkedHashSet<AttributeValue> newValueList =
                new LinkedHashSet<AttributeValue>(valueList.size());
              for (AttributeValue existingValue : valueList)
              {
                long newIntValue;
                try
                {
                  long existingIntValue =
                    Long.parseLong(existingValue.getStringValue());
                  newIntValue = existingIntValue + incrementValue;
                }
                catch (Exception e)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                  }
                  localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
                  localOp.appendErrorMessage(
                          ERR_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE.get(
                                  String.valueOf(entryDN),
                      a.getName(),
                      existingValue.getStringValue()));
                  break modifyProcessing;
                }
                ByteString newValue =
                  new ASN1OctetString(String.valueOf(newIntValue));
                newValueList.add(new AttributeValue(t, newValue));
              }
              attr.setValues(newValueList);
              updated = true;
            }
            if (! updated)
            {
              localOp.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
              localOp.appendErrorMessage(
                  ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get(
                          String.valueOf(entryDN),
                          a.getName()));
              break modifyProcessing;
            }
            break;
          default:
          }
        }
        // If there was a password change, then perform any additional checks
        // that may be necessary.
        if (passwordChanged)
        {
          // If it was a self change, then see if the current password was
          // provided and handle accordingly.
          if (selfChange &&
              pwPolicyState.getPolicy().requireCurrentPassword() &&
              (! currentPasswordProvided))
          {
            pwpErrorType = PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD;
            localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            localOp.appendErrorMessage(
                    ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get());
            break modifyProcessing;
          }
          // If this change would result in multiple password values, then see
          // if that's OK.
          if ((numPasswords > 1) &&
              (! pwPolicyState.getPolicy().allowMultiplePasswordValues()))
          {
            pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
            localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            localOp.appendErrorMessage(
                    ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get());
            break modifyProcessing;
          }
          // If any of the password values should be validated, then do so now.
          if (selfChange ||
              (! pwPolicyState.getPolicy().skipValidationForAdministrators()))
          {
            List<AttributeValue> newPasswords =
              localOp.getNewPasswords();
            List<AttributeValue> currentPasswords =
              localOp.getCurrentPasswords();
            if (newPasswords != null)
            {
              HashSet<ByteString> clearPasswords = new HashSet<ByteString>();
              clearPasswords.addAll(pwPolicyState.getClearPasswords());
              if (currentPasswords != null)
              {
                if (clearPasswords.isEmpty())
                {
                  for (AttributeValue v : currentPasswords)
                  {
                    clearPasswords.add(v.getValue());
                  }
                }
                else
                {
                  // NOTE:  We can't rely on the fact that Set doesn't allow
                  // duplicates because technically it's possible that the
                  // values aren't duplicates if they are ASN.1 elements with
                  // different types (like 0x04 for a standard universal octet
                  // string type versus 0x80 for a simple password in a bind
                  // operation).  So we have to manually check for duplicates.
                  for (AttributeValue v : currentPasswords)
                  {
                    ByteString pw = v.getValue();
                    boolean found = false;
                    for (ByteString s : clearPasswords)
                    {
                      if (Arrays.equals(s.value(), pw.value()))
                      {
                        found = true;
                        break;
                      }
                    }
                    if (! found)
                    {
                      clearPasswords.add(pw);
                    }
                  }
                }
              }
              for (AttributeValue v : newPasswords)
              {
                MessageBuilder invalidReason = new MessageBuilder();
                if (! pwPolicyState.passwordIsAcceptable(localOp, modifiedEntry,
                    v.getValue(),
                    clearPasswords,
                    invalidReason))
                {
                  pwpErrorType =
                       PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
                  localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  localOp.appendErrorMessage(ERR_MODIFY_PW_VALIDATION_FAILED
                          .get(invalidReason.toString()));
                  break modifyProcessing;
                }
              }
            }
          }
          // If we should check the password history, then do so now.
          if (pwPolicyState.maintainHistory())
          {
            List<AttributeValue> newPasswords = localOp.getNewPasswords();
            if (newPasswords != null)
            {
              for (AttributeValue v : newPasswords)
              {
                if (pwPolicyState.isPasswordInHistory(v.getValue()))
                {
                  if (selfChange || (! pwPolicyState.getPolicy().
                                            skipValidationForAdministrators()))
                  {
                    pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
                    localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                    localOp.appendErrorMessage(ERR_MODIFY_PW_IN_HISTORY.get());
                    break modifyProcessing;
                  }
                }
              }
              pwPolicyState.updatePasswordHistory();
            }
          }
        }
        // Check to see if the client has permission to perform the
        // modify.
        // The access control check is not made any earlier because the
        // handler needs access to the modified entry.
        // 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)) {
          localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          localOp.appendErrorMessage(
              ERR_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                      String.valueOf(entryDN)));
          skipPostOperation = true;
          break modifyProcessing;
        }
        boolean wasLocked = false;
        if (passwordChanged)
        {
          // See if the account was locked for any reason.
          wasLocked = pwPolicyState.lockedDueToIdleInterval() ||
                      pwPolicyState.lockedDueToMaximumResetAge() ||
                      pwPolicyState.lockedDueToFailures();
          // Update the password policy state attributes in the user's entry.
          // If the modification fails, then these changes won't be applied.
          pwPolicyState.setPasswordChangedTime();
          pwPolicyState.clearFailureLockout();
          pwPolicyState.clearGraceLoginTimes();
          pwPolicyState.clearWarnedTime();
          if (pwPolicyState.getPolicy().forceChangeOnAdd() ||
              pwPolicyState.getPolicy().forceChangeOnReset())
          {
            if (selfChange)
            {
              pwPolicyState.setMustChangePassword(false);
            }
            else
            {
              if ((pwpErrorType == null) &&
                  pwPolicyState.getPolicy().forceChangeOnReset())
              {
                pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
              }
              pwPolicyState.setMustChangePassword(
                   pwPolicyState.getPolicy().forceChangeOnReset());
            }
          }
          if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
          {
            pwPolicyState.setRequiredChangeTime();
          }
          modifications.addAll(pwPolicyState.getModifications());
          //Apply pwd Policy modifications to modified entry.
          try {
            modifiedEntry.applyModifications(pwPolicyState.getModifications());
          } catch (DirectoryException e) {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
            localOp.setResponseData(e);
            break modifyProcessing;
          }
        }
        else if((! localOp.isInternalOperation()) &&
            pwPolicyState.mustChangePassword())
        {
            // The user will not be allowed to do anything else before
            // the password gets changed.
            pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
            localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            localOp.appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
            break modifyProcessing;
        }
        // If the server is configured to check the schema and the
        // operation is not a sycnhronization operation,
        // make sure that the new entry is valid per the server schema.
        if ((DirectoryServer.checkSchema()) &&
            (!localOp.isSynchronizationOperation()) )
        {
          MessageBuilder invalidReason = new MessageBuilder();
          if (! modifiedEntry.conformsToSchema(null, false, false, false,
              invalidReason))
          {
            localOp.setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
            localOp.appendErrorMessage(ERR_MODIFY_VIOLATES_SCHEMA.get(
                    String.valueOf(entryDN),
                    invalidReason.toString()));
            break modifyProcessing;
          }
        }
        // Check for a request to cancel this operation.
        if (localOp.getCancelRequest() != null)
        {
          return;
        }
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation modify plugins.
        if (!localOp.isSynchronizationOperation())
        {
          PreOperationPluginResult preOpResult =
            pluginConfigManager.invokePreOperationModifyPlugins(localOp);
          if (preOpResult.connectionTerminated())
          {
            // There's no point in continuing with anything.  Log the result
            // and return.
            localOp.setResultCode(ResultCode.CANCELED);
            localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
            localOp.setProcessingStopTime();
            return;
          }
          else if (preOpResult.sendResponseImmediately())
          {
            skipPostOperation = true;
            break modifyProcessing;
          }
          else if (preOpResult.skipCoreProcessing())
          {
            skipPostOperation = false;
            break modifyProcessing;
          }
        }
        // Check for a request to cancel this operation.
        if (localOp.getCancelRequest() != null)
        {
          return;
        }
        // Actually perform the modify operation.  This should also include
        // taking care of any synchronization that might be needed.
        if (backend == null)
        {
          localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
          localOp.appendErrorMessage(
              ERR_MODIFY_NO_BACKEND_FOR_ENTRY.get(String.valueOf(entryDN)));
          break modifyProcessing;
        }
        try
        {
          // If it is not a private backend, then check to see if the server or
          // backend is operating in read-only mode.
          if (! backend.isPrivateBackend())
          {
            switch (DirectoryServer.getWritabilityMode())
            {
            case DISABLED:
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              localOp.appendErrorMessage(
                  ERR_MODIFY_SERVER_READONLY.get(String.valueOf(entryDN)));
              break modifyProcessing;
            case INTERNAL_ONLY:
              if (! (localOp.isInternalOperation() ||
                  localOp.isSynchronizationOperation()))
              {
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                    ERR_MODIFY_SERVER_READONLY.get(String.valueOf(entryDN)));
                break modifyProcessing;
              }
            }
            switch (backend.getWritabilityMode())
            {
            case DISABLED:
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              localOp.appendErrorMessage(
                  ERR_MODIFY_BACKEND_READONLY.get(String.valueOf(entryDN)));
              break modifyProcessing;
            case INTERNAL_ONLY:
              if (! localOp.isInternalOperation() ||
                  localOp.isSynchronizationOperation())
              {
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                    ERR_MODIFY_BACKEND_READONLY.get(String.valueOf(entryDN)));
                break modifyProcessing;
              }
            }
          }
          if (noOp)
          {
            localOp.appendErrorMessage(INFO_MODIFY_NOOP.get());
            localOp.setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
            for (SynchronizationProvider provider :
              DirectoryServer.getSynchronizationProviders())
            {
              try
              {
                SynchronizationProviderResult result =
                  provider.doPreOperation(localOp);
                if (! result.continueOperationProcessing())
                {
                  break modifyProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                logError(ERR_MODIFY_SYNCH_PREOP_FAILED.
                    get(localOp.getConnectionID(), localOp.getOperationID(),
                        getExceptionMessage(de)));
                localOp.setResponseData(de);
                break modifyProcessing;
              }
            }
            backend.replaceEntry(modifiedEntry, localOp);
            // See if we need to generate any account status notifications as a
            // result of the changes.
            if (passwordChanged || enabledStateChanged || wasLocked)
            {
              handleAccountStatusNotifications(passwordChanged, selfChange,
                                               enabledStateChanged, isEnabled,
                                               wasLocked, localOp,
                                               pwPolicyState, modifiedEntry);
            }
          }
          if (preReadRequest != null)
          {
            Entry entry = currentEntry.duplicate(true);
            if (! preReadRequest.allowsAttribute(
                DirectoryServer.getObjectClassAttributeType()))
            {
              entry.removeAttribute(
                  DirectoryServer.getObjectClassAttributeType());
            }
            if (! preReadRequest.returnAllUserAttributes())
            {
              Iterator<AttributeType> iterator =
                entry.getUserAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! preReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            if (! preReadRequest.returnAllOperationalAttributes())
            {
              Iterator<AttributeType> iterator =
                entry.getOperationalAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! preReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            // FIXME -- Check access controls on the entry to see if it should
            //          be returned or if any attributes need to be stripped
            //          out..
            SearchResultEntry searchEntry = new SearchResultEntry(entry);
            LDAPPreReadResponseControl responseControl =
              new LDAPPreReadResponseControl(preReadRequest.getOID(),
                  preReadRequest.isCritical(),
                  searchEntry);
            localOp.getResponseControls().add(responseControl);
          }
          if (postReadRequest != null)
          {
            Entry entry = modifiedEntry.duplicate(true);
            if (! postReadRequest.allowsAttribute(
                DirectoryServer.getObjectClassAttributeType()))
            {
              entry.removeAttribute(
                  DirectoryServer.getObjectClassAttributeType());
            }
            if (! postReadRequest.returnAllUserAttributes())
            {
              Iterator<AttributeType> iterator =
                entry.getUserAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! postReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            if (! postReadRequest.returnAllOperationalAttributes())
            {
              Iterator<AttributeType> iterator =
                entry.getOperationalAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! postReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            // FIXME -- Check access controls on the entry to see if it should
            //          be returned or if any attributes need to be stripped
            //          out..
            SearchResultEntry searchEntry = new SearchResultEntry(entry);
            LDAPPostReadResponseControl responseControl =
              new LDAPPostReadResponseControl(postReadRequest.getOID(),
                  postReadRequest.isCritical(),
                  searchEntry);
            localOp.getResponseControls().add(responseControl);
          }
          if (! noOp)
          {
            localOp.setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          localOp.setResultCode(de.getResultCode());
          localOp.appendErrorMessage(de.getMessageObject());
          localOp.setMatchedDN(de.getMatchedDN());
          localOp.setReferralURLs(de.getReferralURLs());
          break modifyProcessing;
        }
        catch (CancelledOperationException coe)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
          }
          CancelResult cancelResult = coe.getCancelResult();
          localOp.setCancelResult(cancelResult);
          localOp.setResultCode(cancelResult.getResultCode());
          Message message = coe.getMessageObject();
          if ((message != null) && (message.length() > 0))
          {
            localOp.appendErrorMessage(message);
          }
          break modifyProcessing;
        }
      }
      finally
      {
        LockManager.unlock(entryDN, entryLock);
        for (SynchronizationProvider provider :
          DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.doPostOperation(localOp);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_MODIFY_SYNCH_POSTOP_FAILED.
                get(localOp.getConnectionID(), localOp.getOperationID(),
                    getExceptionMessage(de)));
            localOp.setResponseData(de);
            break;
          }
        }
      }
    }
    // If the password policy request control was included, then make sure we
    // send the corresponding response control.
    if (pwPolicyControlRequested)
    {
      localOp.addResponseControl(
           new PasswordPolicyResponseControl(null, 0, pwpErrorType));
    }
    // Indicate that it is now too late to attempt to cancel the operation.
    localOp.setCancelResult(CancelResult.TOO_LATE);
    // Invoke the post-operation or post-synchronization modify plugins.
    if (localOp.isSynchronizationOperation())
    {
      if (localOp.getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationModifyPlugins(localOp);
      }
    }
    else if (! skipPostOperation)
    {
      // FIXME -- Should this also be done while holding the locks?
      PostOperationPluginResult postOpResult =
        pluginConfigManager.invokePostOperationModifyPlugins(localOp);
      if (postOpResult.connectionTerminated())
      {
        // There's no point in continuing with anything.  Log the result and
        // return.
        localOp.setResultCode(ResultCode.CANCELED);
        localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
        localOp.setProcessingStopTime();
        return;
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    if (localOp.getResultCode() == ResultCode.SUCCESS)
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleModifyOperation(localOp,
              localOp.getCurrentEntry(),
              localOp.getModifiedEntry());
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          Message message = ERR_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
          logError(message);
        }
      }
    }
    // Stop the processing timer.
    localOp.setProcessingStopTime();
  }
  /**
   * Handles any account status notifications that may be needed as a result of
   * modify processing using the provided information.
   *
   * @param  passwordChanged      Indicates whether the modify operation
   *                              included a password change.
   * @param  selfChange           Indicates whether the password change was
   *                              performed by the end user or an administrator.
   * @param  enabledStateChanged  Indicates whether the user's account changed
   *                              from enabled to disabled (or vice versa)
   * @param  isEnabled            Indicates whether the user's account is now
   *                              enabled.
   * @param  wasLocked            Indicates whether the user's account was
   *                              previously locked.
   * @param  localOp              The modify operation being processed.
   * @param  pwPolicyState        The password policy state for the user.
   * @param  modifiedEntry        The updated version of the entry.
   */
  private void handleAccountStatusNotifications(boolean passwordChanged,
                    boolean selfChange, boolean enabledStateChanged,
                    boolean isEnabled, boolean wasLocked,
                    LocalBackendModifyOperation  localOp,
                    PasswordPolicyState pwPolicyState, Entry modifiedEntry)
  {
    if (passwordChanged)
    {
      if (selfChange)
      {
        AuthenticationInfo authInfo =
          localOp.getClientConnection().getAuthenticationInfo();
        if (authInfo.getAuthenticationDN().equals(modifiedEntry.getDN()))
        {
          localOp.getClientConnection().setMustChangePassword(false);
        }
        Message message = INFO_MODIFY_PASSWORD_CHANGED.get();
        pwPolicyState.generateAccountStatusNotification(
            AccountStatusNotificationType.PASSWORD_CHANGED,
            modifiedEntry, message,
            AccountStatusNotification.createProperties(pwPolicyState, false, -1,
                 localOp.getCurrentPasswords(), localOp.getNewPasswords()));
      }
      else
      {
        Message message = INFO_MODIFY_PASSWORD_RESET.get();
        pwPolicyState.generateAccountStatusNotification(
            AccountStatusNotificationType.PASSWORD_RESET, modifiedEntry,
            message,
            AccountStatusNotification.createProperties(pwPolicyState, false, -1,
                 localOp.getCurrentPasswords(), localOp.getNewPasswords()));
      }
    }
    if (enabledStateChanged)
    {
      if (isEnabled)
      {
        Message message = INFO_MODIFY_ACCOUNT_ENABLED.get();
        pwPolicyState.generateAccountStatusNotification(
            AccountStatusNotificationType.ACCOUNT_ENABLED,
            modifiedEntry, message,
            AccountStatusNotification.createProperties(pwPolicyState, false, -1,
                 null, null));
      }
      else
      {
        Message message = INFO_MODIFY_ACCOUNT_DISABLED.get();
        pwPolicyState.generateAccountStatusNotification(
            AccountStatusNotificationType.ACCOUNT_DISABLED,
            modifiedEntry, message,
            AccountStatusNotification.createProperties(pwPolicyState, false, -1,
                 null, null));
      }
    }
    if (wasLocked)
    {
      Message message = INFO_MODIFY_ACCOUNT_UNLOCKED.get();
      pwPolicyState.generateAccountStatusNotification(
          AccountStatusNotificationType.ACCOUNT_UNLOCKED, modifiedEntry,
          message,
          AccountStatusNotification.createProperties(pwPolicyState, false, -1,
               null, null));
    }
  }
  /**
   * Perform a search operation against a local backend.
   *
   * @param operation - The operation to perform
   */
  public void processSearch(SearchOperation operation)
  {
    LocalBackendSearchOperation localOp =
      new LocalBackendSearchOperation(operation);
    PersistentSearch persistentSearch = null;
    ClientConnection clientConnection = localOp.getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
      DirectoryServer.getPluginConfigManager();
    boolean skipPostOperation = 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.
      DN baseDN = localOp.getBaseDN();
      SearchFilter filter = localOp.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.
      boolean       processSearch    = true;
      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 (!AccessControlConfigManager.getInstance().
                  getAccessControlHandler().
                  isAllowed(baseDN, localOp, c))
          {
            localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
            localOp.appendErrorMessage(
                    ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
            skipPostOperation = true;
            break searchProcessing;
          }
          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.getMessageObject());
                break searchProcessing;
              }
            }
            try
            {
              // FIXME -- We need to determine whether the current user has
              //          permission to make this determination.
              SearchFilter assertionFilter = assertControl.getSearchFilter();
              Entry entry;
              try
              {
                entry = DirectoryServer.getEntry(baseDN);
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(de.getResultCode());
                localOp.appendErrorMessage(
                    ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(
                            de.getMessageObject()));
                break searchProcessing;
              }
              if (entry == null)
              {
                localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
                localOp.appendErrorMessage(
                    ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
                break searchProcessing;
              }
              if (! assertionFilter.matchesEntry(entry))
              {
                localOp.setResultCode(ResultCode.ASSERTION_FAILED);
                localOp.appendErrorMessage(
                    ERR_SEARCH_ASSERTION_FAILED.get());
                break searchProcessing;
              }
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
              localOp.appendErrorMessage(
                  ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
                          de.getMessageObject()));
              break searchProcessing;
            }
          }
          else if (oid.equals(OID_PROXIED_AUTH_V1))
          {
            // The requester must have the PROXIED_AUTH privilige in order to be
            // able to use this control.
            if (! clientConnection.hasPrivilege(
                Privilege.PROXIED_AUTH, localOp))
            {
              localOp.appendErrorMessage(
                      ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
              localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
              break searchProcessing;
            }
            ProxiedAuthV1Control proxyControl;
            if (c instanceof ProxiedAuthV1Control)
            {
              proxyControl = (ProxiedAuthV1Control) c;
            }
            else
            {
              try
              {
                proxyControl = ProxiedAuthV1Control.decodeControl(c);
              }
              catch (LDAPException le)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
                }
                localOp.setResultCode(
                    ResultCode.valueOf(le.getResultCode()));
                localOp.appendErrorMessage(le.getMessageObject());
                break searchProcessing;
              }
            }
            Entry authorizationEntry;
            try
            {
              authorizationEntry = proxyControl.getAuthorizationEntry();
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              localOp.setResultCode(de.getResultCode());
              localOp.appendErrorMessage(de.getMessageObject());
              break searchProcessing;
            }
            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))
            {
              localOp.appendErrorMessage(
                      ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
              localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
              break searchProcessing;
            }
            ProxiedAuthV2Control proxyControl;
            if (c instanceof ProxiedAuthV2Control)
            {
              proxyControl = (ProxiedAuthV2Control) c;
            }
            else
            {
              try
              {
                proxyControl = ProxiedAuthV2Control.decodeControl(c);
              }
              catch (LDAPException le)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
                }
                localOp.setResultCode(
                    ResultCode.valueOf(le.getResultCode()));
                localOp.appendErrorMessage(le.getMessageObject());
                break searchProcessing;
              }
            }
            Entry authorizationEntry;
            try
            {
              authorizationEntry = proxyControl.getAuthorizationEntry();
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              localOp.setResultCode(de.getResultCode());
              localOp.appendErrorMessage(de.getMessageObject());
              break searchProcessing;
            }
            localOp.setAuthorizationEntry(authorizationEntry);
            if (authorizationEntry == null)
            {
              localOp.setProxiedAuthorizationDN(DN.nullDN());
            }
            else
            {
              localOp.setProxiedAuthorizationDN(authorizationEntry.getDN());
            }
          }
          else if (oid.equals(OID_PERSISTENT_SEARCH))
          {
            PersistentSearchControl psearchControl;
            if (c instanceof PersistentSearchControl)
            {
              psearchControl = (PersistentSearchControl) c;
            }
            else
            {
              try
              {
                psearchControl = PersistentSearchControl.decodeControl(c);
              }
              catch (LDAPException le)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
                }
                localOp.setResultCode(
                    ResultCode.valueOf(le.getResultCode()));
                localOp.appendErrorMessage(le.getMessageObject());
                break searchProcessing;
              }
            }
            persistentSearch =
              new PersistentSearch(operation, psearchControl.getChangeTypes(),
                  psearchControl.getReturnECs());
            localOp.setPersistentSearch(persistentSearch);
            // If we're only interested in changes, then we don't actually want
            // to process the search now.
            if (psearchControl.getChangesOnly())
            {
              processSearch = false;
            }
          }
          else if (oid.equals(OID_LDAP_SUBENTRIES))
          {
            localOp.setReturnLDAPSubentries(true);
          }
          else if (oid.equals(OID_MATCHED_VALUES))
          {
            if (c instanceof MatchedValuesControl)
            {
              localOp.setMatchedValuesControl((MatchedValuesControl) c);
            }
            else
            {
              try
              {
                MatchedValuesControl matchedValuesControl =
                  MatchedValuesControl.decodeControl(c);
                localOp.setMatchedValuesControl(matchedValuesControl);
              }
              catch (LDAPException le)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
                }
                localOp.setResultCode(
                    ResultCode.valueOf(le.getResultCode()));
                localOp.appendErrorMessage(le.getMessageObject());
                break searchProcessing;
              }
            }
          }
          else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
          {
            localOp.setIncludeUsableControl(true);
          }
          else if (oid.equals(OID_REAL_ATTRS_ONLY))
          {
            localOp.setRealAttributesOnly(true);
          }
          else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
          {
            localOp.setVirtualAttributesOnly(true);
          }
          // NYI -- Add support for additional controls.
          else if (c.isCritical())
          {
            if ((backend == null) || (! backend.supportsControl(oid)))
            {
              localOp.setResultCode(
                  ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
              localOp.appendErrorMessage(
                      ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
              break searchProcessing;
            }
          }
        }
      }
      // Check to see if the client has permission to perform the
      // search.
      // FIXME: for now assume that this will check all permission
      // pertinent to the operation. This includes proxy authorization
      // and any other controls specified.
      if (AccessControlConfigManager.getInstance()
          .getAccessControlHandler().isAllowed(localOp) == false) {
        localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        localOp.appendErrorMessage(
            ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                    String.valueOf(baseDN)));
        skipPostOperation = true;
        break searchProcessing;
      }
      // Check for a request to cancel this operation.
      if (localOp.getCancelRequest() != null)
      {
        return;
      }
      // Invoke the pre-operation search plugins.
      PreOperationPluginResult preOpResult =
        pluginConfigManager.invokePreOperationSearchPlugins(localOp);
      if (preOpResult.connectionTerminated())
      {
        // There's no point in continuing with anything.  Log the request and
        // result and return.
        localOp.setResultCode(ResultCode.CANCELED);
        localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
        localOp.setProcessingStopTime();
        return;
      }
      else if (preOpResult.sendResponseImmediately())
      {
        skipPostOperation = true;
        break searchProcessing;
      }
      else if (preOpResult.skipCoreProcessing())
      {
        skipPostOperation = false;
        break searchProcessing;
      }
      // Check for a request to cancel this operation.
      if (localOp.getCancelRequest() != null)
      {
        return;
      }
      // Get the backend that should hold the search base.  If there is none,
      // then fail.
      if (backend == null)
      {
        localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
        localOp.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.
      localOp.setResultCode(ResultCode.SUCCESS);
      // If there's a persistent search, then register it with the server.
      if (persistentSearch != null)
      {
        DirectoryServer.registerPersistentSearch(persistentSearch);
        localOp.setSendResponse(false);
      }
      // Process the search in the backend and all its subordinates.
      try
      {
        if (processSearch)
        {
          localOp.searchBackend(backend);
        }
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        localOp.setResultCode(de.getResultCode());
        localOp.appendErrorMessage(de.getMessageObject());
        localOp.setMatchedDN(de.getMatchedDN());
        localOp.setReferralURLs(de.getReferralURLs());
        if (persistentSearch != null)
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          localOp.setSendResponse(true);
        }
        break searchProcessing;
      }
      catch (CancelledOperationException coe)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, coe);
        }
        CancelResult cancelResult = coe.getCancelResult();
        localOp.setCancelResult(cancelResult);
        localOp.setResultCode(cancelResult.getResultCode());
        Message message = coe.getMessageObject();
        if ((message != null) && (message.length() > 0))
        {
          localOp.appendErrorMessage(message);
        }
        if (persistentSearch != null)
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          localOp.setSendResponse(true);
        }
        skipPostOperation = true;
        break searchProcessing;
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        localOp.setResultCode(
            DirectoryServer.getServerErrorResultCode());
        localOp.appendErrorMessage(
            ERR_SEARCH_BACKEND_EXCEPTION.get(getExceptionMessage(e)));
        if (persistentSearch != null)
        {
          DirectoryServer.deregisterPersistentSearch(persistentSearch);
          localOp.setSendResponse(true);
        }
        skipPostOperation = true;
        break searchProcessing;
      }
    }
    // Check for a request to cancel this operation.
    if (localOp.getCancelRequest() != null)
    {
      return;
    }
    // Invoke the post-operation search plugins.
    if (! skipPostOperation)
    {
      PostOperationPluginResult postOperationResult =
        pluginConfigManager.invokePostOperationSearchPlugins(localOp);
      if (postOperationResult.connectionTerminated())
      {
        localOp.setResultCode(ResultCode.CANCELED);
        localOp.appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
        localOp.setProcessingStopTime();
        return;
      }
    }
  }
  /**
   * Perform a bind operation against a local backend.
   *
   * @param operation - The operation to perform
   */
  public void processBind(BindOperation operation)
  {
    LocalBackendBindOperation localOperation =
        new LocalBackendBindOperation(operation);
    processLocalBind(localOperation);
  }
  /**
   * Perform a local bind operation against a local backend.
   *
   * @param localOp - The operation to perform
   */
  private void processLocalBind(LocalBackendBindOperation localOp)
  {
    ClientConnection clientConnection = localOp.getClientConnection();
    boolean returnAuthzID    = false;
    int     sizeLimit        = DirectoryServer.getSizeLimit();
    int     timeLimit        = DirectoryServer.getTimeLimit();
    int     lookthroughLimit = DirectoryServer.getLookthroughLimit();
    long    idleTimeLimit    = DirectoryServer.getIdleTimeLimit();
    boolean skipPostOperation = false;
    // The password policy state information for this bind operation.
    PasswordPolicyState pwPolicyState = null;
    // The password policy error type that should be included in the response
    // control
    PasswordPolicyErrorType pwPolicyErrorType = null;
    // Indicates whether the client included the password policy control in the
    // bind request.
    boolean pwPolicyControlRequested = false;
    // Indicates whether the authentication should use a grace login if it is
    // successful.
    boolean isGraceLogin = false;
    // Indicates whether the user's password must be changed before any other
    // operations will be allowed.
    boolean mustChangePassword = false;
    // The password policy warning type that should be included in the response
    // control
    PasswordPolicyWarningType pwPolicyWarningType = null;
    // The password policy warning value that should be included in the response
    // control.
    int pwPolicyWarningValue = -1 ;
    String saslMechanism = localOp.getSASLMechanism();
    // Indicates whether the warning notification that should be sent to
    // the user would be the first warning.
    boolean isFirstWarning = false;
    // The entry of the user that successfully authenticated during processing
    // of this bind operation.
    Entry authenticatedUserEntry = null;
    // The password policy state information for this bind operation.
    pwPolicyState = null;
    // 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.
bindProcessing:
    {
      DN bindDN = localOp.getBindDN();
      // Check to see if the client has permission to perform the
      // bind.
      // FIXME: for now assume that this will check all permission
      // pertinent to the operation. This includes any controls
      // specified.
      if (AccessControlConfigManager.getInstance()
          .getAccessControlHandler().isAllowed(localOp) == false) {
        localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                String.valueOf(bindDN));
        localOp.setAuthFailureReason(message);
        skipPostOperation = true;
        break bindProcessing;
      }
      // 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 (!AccessControlConfigManager.getInstance().
                  getAccessControlHandler(). isAllowed(bindDN, localOp, c))
          {
            localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
            localOp.appendErrorMessage(ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS
                    .get(oid));
            skipPostOperation = true;
            break bindProcessing;
          }
          if (oid.equals(OID_AUTHZID_REQUEST))
          {
            returnAuthzID = true;
          }
          else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
          {
            pwPolicyControlRequested = true;
          }
          // NYI -- Add support for additional controls.
          else if (c.isCritical())
          {
            localOp.setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
            localOp.appendErrorMessage(ERR_BIND_UNSUPPORTED_CRITICAL_CONTROL
                    .get(String.valueOf(oid)));
            break bindProcessing;
          }
        }
      }
      // Check to see if this is a simple bind or a SASL bind and process
      // accordingly.
      switch (localOp.getAuthenticationType())
      {
        case SIMPLE:
          // See if this is an anonymous bind.  If so, then determine whether
          // to allow it.
          ByteString simplePassword = localOp.getSimplePassword();
          if ((simplePassword == null) || (simplePassword.value().length == 0))
          {
            // If the server is in lockdown mode, then fail.
            if (DirectoryServer.lockdownMode())
            {
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              Message message = ERR_BIND_REJECTED_LOCKDOWN_MODE.get();
              localOp.setAuthFailureReason(message);
              localOp.setProcessingStopTime();
              logBindResponse(localOp);
              break bindProcessing;
            }
            // If there is a bind DN, then see whether that is acceptable.
            if (DirectoryServer.bindWithDNRequiresPassword() &&
                ((bindDN != null) && (! bindDN.isNullDN())))
            {
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              Message message = ERR_BIND_DN_BUT_NO_PASSWORD.get();
              localOp.setAuthFailureReason(message);
              break bindProcessing;
            }
            // Invoke the pre-operation bind plugins.
            PreOperationPluginResult preOpResult =
                 pluginConfigManager.invokePreOperationBindPlugins(localOp);
            if (preOpResult.connectionTerminated())
            {
              // There's no point in continuing with anything.  Log the result
              // and return.
              localOp.setResultCode(ResultCode.CANCELED);
              localOp.appendErrorMessage(
                      ERR_CANCELED_BY_PREOP_DISCONNECT.get());
              return;
            }
            else if (preOpResult.sendResponseImmediately())
            {
              skipPostOperation = true;
              break bindProcessing;
            }
            else if (preOpResult.skipCoreProcessing())
            {
              skipPostOperation = false;
              break bindProcessing;
            }
            localOp.setResultCode(ResultCode.SUCCESS);
            localOp.setAuthenticationInfo(new AuthenticationInfo());
            break bindProcessing;
          }
          // See if the bind DN is actually one of the alternate root DNs
          // defined in the server.  If so, then replace it with the actual DN
          // for that user.
          DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN);
          if (actualRootDN != null)
          {
            bindDN = actualRootDN;
          }
          // Get the user entry based on the bind DN.  If it does not exist,
          // then fail.
          Lock userLock = null;
          for (int i=0; i < 3; i++)
          {
            userLock = LockManager.lockRead(bindDN);
            if (userLock != null)
            {
              break;
            }
          }
          if (userLock == null)
          {
            Message message = ERR_BIND_OPERATION_CANNOT_LOCK_USER.get(
                    String.valueOf(bindDN));
            localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
            localOp.setAuthFailureReason(message);
            break bindProcessing;
          }
          try
          {
            Entry userEntry;
            try
            {
              userEntry = backend.getEntry(bindDN);
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(de.getMessageObject());
              userEntry = null;
              break bindProcessing;
            }
            if (userEntry == null)
            {
              Message message = ERR_BIND_OPERATION_UNKNOWN_USER.get(
                      String.valueOf(bindDN));
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(message);
              break bindProcessing;
            }
            else
            {
              localOp.setUserEntryDN(userEntry.getDN());
            }
            // Check to see if the user has a password.  If not, then fail.
            // FIXME -- We need to have a way to enable/disable debugging.
            pwPolicyState = new PasswordPolicyState(userEntry, false, false);
            PasswordPolicy policy = pwPolicyState.getPolicy();
            AttributeType  pwType = policy.getPasswordAttribute();
            List<Attribute> pwAttr = userEntry.getAttribute(pwType);
            if ((pwAttr == null) || (pwAttr.isEmpty()))
            {
              Message message = ERR_BIND_OPERATION_NO_PASSWORD.get(
                      String.valueOf(bindDN));
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(message);
              break bindProcessing;
            }
            // If the password policy is configured to track authentication
            // failures or keep the last login time and the associated backend
            // is disabled, then we may need to reject the bind immediately.
            if ((policy.getStateUpdateFailurePolicy() ==
                 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) &&
                ((policy.getLockoutFailureCount() > 0) ||
                 ((policy.getLastLoginTimeAttribute() != null) &&
                  (policy.getLastLoginTimeFormat() != null))) &&
                ((DirectoryServer.getWritabilityMode() ==
                  WritabilityMode.DISABLED) ||
                 (backend.getWritabilityMode() == WritabilityMode.DISABLED)))
            {
              // This policy isn't applicable to root users, so if it's a root
              // user then ignore it.
              if (! DirectoryServer.isRootDN(bindDN))
              {
                Message message = ERR_BIND_OPERATION_WRITABILITY_DISABLED.get(
                        String.valueOf(bindDN));
                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                localOp.setAuthFailureReason(message);
                break bindProcessing;
              }
            }
            // Check to see if the authentication must be done in a secure
            // manner.  If so, then the client connection must be secure.
            if (policy.requireSecureAuthentication() &&
                (! clientConnection.isSecure()))
            {
              Message message = ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get(
                      String.valueOf(bindDN));
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(message);
              break bindProcessing;
            }
            // Check to see if the user is administratively disabled or locked.
            if (pwPolicyState.isDisabled())
            {
              Message message = ERR_BIND_OPERATION_ACCOUNT_DISABLED.get(
                      String.valueOf(bindDN));
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(message);
              break bindProcessing;
            }
            else if (pwPolicyState.isAccountExpired())
            {
              Message message = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get(
                      String.valueOf(bindDN));
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_EXPIRED, userEntry,
                   message,
                   AccountStatusNotification.createProperties(pwPolicyState,
                         false, -1, null, null));
              break bindProcessing;
            }
            else if (pwPolicyState.lockedDueToFailures())
            {
              Message message = ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get(
                      String.valueOf(bindDN));
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(message);
              break bindProcessing;
            }
            else if (pwPolicyState.lockedDueToMaximumResetAge())
            {
              Message message = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get(
                      String.valueOf(bindDN));
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_RESET_LOCKED,
                   userEntry, message,
                   AccountStatusNotification.createProperties(pwPolicyState,
                         false, -1, null, null));
              break bindProcessing;
            }
            else if (pwPolicyState.lockedDueToIdleInterval())
            {
              Message message = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get(
                      String.valueOf(bindDN));
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, userEntry,
                   message,
                   AccountStatusNotification.createProperties(pwPolicyState,
                         false, -1, null, null));
              break bindProcessing;
            }
            // Determine whether the password is expired, or whether the user
            // should be warned about an upcoming expiration.
            if (pwPolicyState.isPasswordExpired())
            {
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
              }
              int maxGraceLogins = policy.getGraceLoginCount();
              if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
              {
                List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes();
                if ((graceLoginTimes == null) ||
                    (graceLoginTimes.size() < maxGraceLogins))
                {
                  isGraceLogin       = true;
                  mustChangePassword = true;
                  if (pwPolicyWarningType == null)
                  {
                    pwPolicyWarningType =
                         PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
                    pwPolicyWarningValue = maxGraceLogins -
                                           (graceLoginTimes.size() + 1);
                  }
                }
                else
                {
                  Message message = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(
                          String.valueOf(bindDN));
                  localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                  localOp.setAuthFailureReason(message);
                  pwPolicyState.generateAccountStatusNotification(
                       AccountStatusNotificationType.PASSWORD_EXPIRED,
                       userEntry, message,
                       AccountStatusNotification.createProperties(pwPolicyState,
                             false, -1, null, null));
                  break bindProcessing;
                }
              }
              else
              {
                Message message = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(
                        String.valueOf(bindDN));
                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                localOp.setAuthFailureReason(message);
                pwPolicyState.generateAccountStatusNotification(
                     AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry,
                     message,
                     AccountStatusNotification.createProperties(pwPolicyState,
                           false, -1, null, null));
                break bindProcessing;
              }
            }
            else if (pwPolicyState.shouldWarn())
            {
              int numSeconds = pwPolicyState.getSecondsUntilExpiration();
              if (pwPolicyWarningType == null)
              {
                pwPolicyWarningType =
                     PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
                pwPolicyWarningValue = numSeconds;
              }
              isFirstWarning = pwPolicyState.isFirstWarning();
            }
            // Check to see if the user's password has been reset.
            if (pwPolicyState.mustChangePassword())
            {
              mustChangePassword = true;
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
              }
            }
            // Invoke the pre-operation bind plugins.
            PreOperationPluginResult preOpResult =
                 pluginConfigManager.invokePreOperationBindPlugins(localOp);
            if (preOpResult.connectionTerminated())
            {
              // There's no point in continuing with anything.  Log the result
              // and return.
              localOp.setResultCode(ResultCode.CANCELED);
              localOp.appendErrorMessage(
                      ERR_CANCELED_BY_PREOP_DISCONNECT.get());
              return;
            }
            else if (preOpResult.sendResponseImmediately())
            {
              skipPostOperation = true;
              break bindProcessing;
            }
            else if (preOpResult.skipCoreProcessing())
            {
              skipPostOperation = false;
              break bindProcessing;
            }
            // Determine whether the provided password matches any of the stored
            // passwords for the user.
            if (pwPolicyState.passwordMatches(simplePassword))
            {
              localOp.setResultCode(ResultCode.SUCCESS);
              boolean isRoot = DirectoryServer.isRootDN(userEntry.getDN());
              if (DirectoryServer.lockdownMode() && (! isRoot))
              {
                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                Message message = ERR_BIND_REJECTED_LOCKDOWN_MODE.get();
                localOp.setAuthFailureReason(message);
                break bindProcessing;
              }
              localOp.setAuthenticationInfo(new AuthenticationInfo(
                                                userEntry,
                                                simplePassword,
                                                isRoot));
              // See if the user's entry contains a custom size limit.
              AttributeType attrType =
                   DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT,
                                                 true);
              List<Attribute> attrList = userEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    Message message = WARN_BIND_MULTIPLE_USER_SIZE_LIMITS.get(
                        String.valueOf(userEntry.getDN()));
                    logError(message);
                  }
                  else
                  {
                    try
                    {
                      sizeLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      Message message = WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT
                              .get(v.getStringValue(),
                                String.valueOf(userEntry.getDN()));
                      logError(message);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom time limit.
              attrType =
                   DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT,
                                                 true);
              attrList = userEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    Message message = WARN_BIND_MULTIPLE_USER_TIME_LIMITS.get(
                        String.valueOf(userEntry.getDN()));
                    logError(message);
                  }
                  else
                  {
                    try
                    {
                      timeLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      Message message =
                          WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT.
                            get(v.getStringValue(),
                                String.valueOf(userEntry.getDN()));
                      logError(message);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom idle time limit.
              attrType = DirectoryServer.getAttributeType(
                              OP_ATTR_USER_IDLE_TIME_LIMIT, true);
              attrList = userEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    Message message = WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS.
                        get(String.valueOf(userEntry.getDN()));
                    logError(message);
                  }
                  else
                  {
                    try
                    {
                      idleTimeLimit =
                           1000L * Long.parseLong(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      Message message =
                          WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT.
                            get(v.getStringValue(),
                                String.valueOf(userEntry.getDN()));
                      logError(message);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom lookthrough limit.
              attrType =
                   DirectoryServer.getAttributeType(
                       OP_ATTR_USER_LOOKTHROUGH_LIMIT, true);
              attrList = userEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    Message message =
                        WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS.
                          get(String.valueOf(userEntry.getDN()));
                    logError(message);
                  }
                  else
                  {
                    try
                    {
                      lookthroughLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      Message message =
                          WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT.
                            get(v.getStringValue(),
                                String.valueOf(userEntry.getDN()));
                      logError(message);
                    }
                  }
                }
              }
              pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
              pwPolicyState.clearFailureLockout();
              if (isFirstWarning)
              {
                pwPolicyState.setWarnedTime();
                int numSeconds = pwPolicyState.getSecondsUntilExpiration();
                Message timeToExpiration = secondsToTimeString(numSeconds);
                Message message = WARN_BIND_PASSWORD_EXPIRING.get(
                        timeToExpiration);
                pwPolicyState.generateAccountStatusNotification(
                     AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry,
                     message,
                     AccountStatusNotification.createProperties(pwPolicyState,
                           false, numSeconds, null, null));
              }
              if (isGraceLogin)
              {
                pwPolicyState.updateGraceLoginTimes();
              }
              pwPolicyState.setLastLoginTime();
            }
            else
            {
              Message message = ERR_BIND_OPERATION_WRONG_PASSWORD.get();
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(message);
              if (policy.getLockoutFailureCount() > 0)
              {
                pwPolicyState.updateAuthFailureTimes();
                if (pwPolicyState.lockedDueToFailures())
                {
                  AccountStatusNotificationType notificationType;
                  boolean tempLocked;
                  int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
                  if (lockoutDuration > -1)
                  {
                    notificationType = AccountStatusNotificationType.
                                            ACCOUNT_TEMPORARILY_LOCKED;
                    tempLocked = true;
                    message = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get(
                            secondsToTimeString(lockoutDuration));
                  }
                  else
                  {
                    notificationType = AccountStatusNotificationType.
                                            ACCOUNT_PERMANENTLY_LOCKED;
                    tempLocked = false;
                    message = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
                  }
                  pwPolicyState.generateAccountStatusNotification(
                       notificationType, userEntry, message,
                       AccountStatusNotification.createProperties(pwPolicyState,
                             tempLocked, -1, null, null));
                }
              }
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
            Message message = ERR_BIND_OPERATION_PASSWORD_VALIDATION_EXCEPTION
                    .get(getExceptionMessage(e));
            localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
            localOp.setAuthFailureReason(message);
            break bindProcessing;
          }
          finally
          {
            // No matter what, make sure to unlock the user's entry.
            LockManager.unlock(bindDN, userLock);
          }
          break;
        case SASL:
          // Get the appropriate authentication handler for this request based
          // on the SASL mechanism.  If there is none, then fail.
          SASLMechanismHandler saslHandler =
               DirectoryServer.getSASLMechanismHandler(saslMechanism);
          if (saslHandler == null)
          {
            localOp.setResultCode(ResultCode.AUTH_METHOD_NOT_SUPPORTED);
            Message message = ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get(
                    saslMechanism);
            localOp.appendErrorMessage(message);
            localOp.setAuthFailureReason(message);
            break bindProcessing;
          }
          // Check to see if the client has sufficient permission to perform the
          // bind.
          // NYI
          // Invoke the pre-operation bind plugins.
          PreOperationPluginResult preOpResult =
               pluginConfigManager.invokePreOperationBindPlugins(localOp);
          if (preOpResult.connectionTerminated())
          {
            // There's no point in continuing with anything.  Log the result
            // and return.
            localOp.setResultCode(ResultCode.CANCELED);
            localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
            return;
          }
          else if (preOpResult.sendResponseImmediately())
          {
            skipPostOperation = true;
            break bindProcessing;
          }
          else if (preOpResult.skipCoreProcessing())
          {
            skipPostOperation = false;
            break bindProcessing;
          }
          // Actually process the SASL bind.
          saslHandler.processSASLBind(localOp);
          // If the server is operating in lockdown mode, then we will need to
          // ensure that the authentication was successful and performed as a
          // root user to continue.
          if (DirectoryServer.lockdownMode())
          {
            ResultCode resultCode = localOp.getResultCode();
            if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS)
            {
              if ((resultCode != ResultCode.SUCCESS) ||
                  (localOp.getSASLAuthUserEntry() == null) ||
                  (! DirectoryServer.isRootDN(
                      localOp.getSASLAuthUserEntry().getDN())))
              {
                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                Message message = ERR_BIND_REJECTED_LOCKDOWN_MODE.get();
                localOp.setAuthFailureReason(message);
                break bindProcessing;
              }
            }
          }
          // Create the password policy state object.
          String userDNString;
          Entry saslAuthUserEntry = localOp.getSASLAuthUserEntry();
          if (saslAuthUserEntry == null)
          {
            pwPolicyState = null;
            userDNString  = null;
          }
          else
          {
            try
            {
              // FIXME -- Need to have a way to enable debugging.
              pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false,
                                                      false);
              localOp.setUserEntryDN(saslAuthUserEntry.getDN());
              userDNString = String.valueOf(localOp.getUserEntryDN());
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              localOp.setResponseData(de);
              break bindProcessing;
            }
          }
          // Perform password policy checks that will need to be completed
          // regardless of whether the authentication was successful.
          if (pwPolicyState != null)
          {
            PasswordPolicy policy = pwPolicyState.getPolicy();
            // If the password policy is configured to track authentication
            // failures or keep the last login time and the associated backend
            // is disabled, then we may need to reject the bind immediately.
            if ((policy.getStateUpdateFailurePolicy() ==
                 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) &&
                ((policy.getLockoutFailureCount() > 0) ||
                 ((policy.getLastLoginTimeAttribute() != null) &&
                  (policy.getLastLoginTimeFormat() != null))) &&
                ((DirectoryServer.getWritabilityMode() ==
                  WritabilityMode.DISABLED) ||
                 (backend.getWritabilityMode() == WritabilityMode.DISABLED)))
            {
              // This policy isn't applicable to root users, so if it's a root
              // user then ignore it.
              if (! DirectoryServer.isRootDN(bindDN))
              {
                Message message = ERR_BIND_OPERATION_WRITABILITY_DISABLED.get(
                        userDNString);
                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                localOp.setAuthFailureReason(message);
                break bindProcessing;
              }
            }
            else if (pwPolicyState.isDisabled())
            {
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              Message message = ERR_BIND_OPERATION_ACCOUNT_DISABLED.get(
                      userDNString);
              localOp.setAuthFailureReason(message);
              break bindProcessing;
            }
            else if (pwPolicyState.isAccountExpired())
            {
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              Message message = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get(
                      userDNString);
              localOp.setAuthFailureReason(message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_EXPIRED,
                   saslAuthUserEntry, message,
                   AccountStatusNotification.createProperties(pwPolicyState,
                         false, -1, null, null));
              break bindProcessing;
            }
            if (policy.requireSecureAuthentication() &&
                (! clientConnection.isSecure()) &&
                (! saslHandler.isSecure(saslMechanism)))
            {
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              Message message = ERR_BIND_OPERATION_INSECURE_SASL_BIND.get(
                      saslMechanism, userDNString);
              localOp.setAuthFailureReason(message);
              break bindProcessing;
            }
            if (pwPolicyState.lockedDueToFailures())
            {
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              Message message = ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get(
                      userDNString);
              localOp.setAuthFailureReason(message);
              break bindProcessing;
            }
            if (pwPolicyState.lockedDueToIdleInterval())
            {
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              Message message = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get(
                      userDNString);
              localOp.setAuthFailureReason(message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED,
                   saslAuthUserEntry, message,
                   AccountStatusNotification.createProperties(pwPolicyState,
                         false, -1, null, null));
              break bindProcessing;
            }
            if (saslHandler.isPasswordBased(saslMechanism))
            {
              if (pwPolicyState.lockedDueToMaximumResetAge())
              {
                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                if (pwPolicyErrorType == null)
                {
                  pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
                }
                Message message = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get(
                        userDNString);
                localOp.setAuthFailureReason(message);
                pwPolicyState.generateAccountStatusNotification(
                     AccountStatusNotificationType.ACCOUNT_RESET_LOCKED,
                     saslAuthUserEntry, message,
                     AccountStatusNotification.createProperties(pwPolicyState,
                           false, -1, null, null));
                break bindProcessing;
              }
              if (pwPolicyState.isPasswordExpired())
              {
                if (pwPolicyErrorType == null)
                {
                  pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
                }
                int maxGraceLogins = policy.getGraceLoginCount();
                if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
                {
                  List<Long> graceLoginTimes =
                       pwPolicyState.getGraceLoginTimes();
                  if ((graceLoginTimes == null) ||
                      (graceLoginTimes.size() < maxGraceLogins))
                  {
                    isGraceLogin       = true;
                    mustChangePassword = true;
                    if (pwPolicyWarningType == null)
                    {
                      pwPolicyWarningType =
                           PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
                      pwPolicyWarningValue =
                           maxGraceLogins - (graceLoginTimes.size() + 1);
                    }
                  }
                  else
                  {
                    Message message = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(
                            String.valueOf(bindDN));
                    localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                    localOp.setAuthFailureReason(message);
                    pwPolicyState.generateAccountStatusNotification(
                         AccountStatusNotificationType.PASSWORD_EXPIRED,
                         saslAuthUserEntry, message,
                         AccountStatusNotification.createProperties(
                              pwPolicyState, false, -1, null, null));
                    break bindProcessing;
                  }
                }
                else
                {
                  Message message = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get(
                          String.valueOf(bindDN));
                  localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                  localOp.setAuthFailureReason(message);
                  pwPolicyState.generateAccountStatusNotification(
                       AccountStatusNotificationType.PASSWORD_EXPIRED,
                       saslAuthUserEntry, message,
                       AccountStatusNotification.createProperties(pwPolicyState,
                             false, -1, null, null));
                  break bindProcessing;
                }
              }
              else if (pwPolicyState.shouldWarn())
              {
                int numSeconds = pwPolicyState.getSecondsUntilExpiration();
                if (pwPolicyWarningType == null)
                {
                  pwPolicyWarningType =
                       PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
                  pwPolicyWarningValue = numSeconds;
                }
                isFirstWarning = pwPolicyState.isFirstWarning();
              }
            }
          }
          // Determine whether the authentication was successful and perform
          // any remaining password policy processing accordingly.  Also check
          // for a custom size/time limit.
          ResultCode resultCode = localOp.getResultCode();
          if (resultCode == ResultCode.SUCCESS)
          {
            if (pwPolicyState != null)
            {
              if (saslHandler.isPasswordBased(saslMechanism) &&
                  pwPolicyState.mustChangePassword())
              {
                mustChangePassword = true;
              }
              if (isFirstWarning)
              {
                pwPolicyState.setWarnedTime();
                int numSeconds = pwPolicyState.getSecondsUntilExpiration();
                Message timeToExpiration = secondsToTimeString(numSeconds);
                Message message = WARN_BIND_PASSWORD_EXPIRING.get(
                        timeToExpiration);
                pwPolicyState.generateAccountStatusNotification(
                     AccountStatusNotificationType.PASSWORD_EXPIRING,
                     saslAuthUserEntry, message,
                     AccountStatusNotification.createProperties(pwPolicyState,
                           false, numSeconds, null, null));
              }
              if (isGraceLogin)
              {
                pwPolicyState.updateGraceLoginTimes();
              }
              pwPolicyState.setLastLoginTime();
              // See if the user's entry contains a custom size limit.
              AttributeType attrType =
                   DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT,
                                                 true);
              List<Attribute> attrList =
                   saslAuthUserEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    Message message =
                        WARN_BIND_MULTIPLE_USER_SIZE_LIMITS.get(userDNString);
                    logError(message);
                  }
                  else
                  {
                    try
                    {
                      sizeLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      Message message =
                          WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT.
                            get(v.getStringValue(), userDNString);
                      logError(message);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom time limit.
              attrType =
                   DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT,
                                                    true);
              attrList = saslAuthUserEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    Message message =
                        WARN_BIND_MULTIPLE_USER_TIME_LIMITS.get(userDNString);
                    logError(message);
                  }
                  else
                  {
                    try
                    {
                      timeLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      Message message =
                          WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT.
                            get(v.getStringValue(), userDNString);
                      logError(message);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom idle time limit.
              attrType = DirectoryServer.getAttributeType(
                              OP_ATTR_USER_IDLE_TIME_LIMIT, true);
              attrList = saslAuthUserEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    Message message = WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS.
                        get(String.valueOf(userDNString));
                    logError(message);
                  }
                  else
                  {
                    try
                    {
                      idleTimeLimit =
                           1000L * Long.parseLong(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      Message message =
                          WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT.
                            get(v.getStringValue(),
                                String.valueOf(userDNString));
                      logError(message);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom lookthrough limit.
              attrType =
                   DirectoryServer.getAttributeType(
                       OP_ATTR_USER_LOOKTHROUGH_LIMIT, true);
              attrList = saslAuthUserEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    Message message =
                        WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS.
                          get(userDNString);
                    logError(message);
                  }
                  else
                  {
                    try
                    {
                      lookthroughLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      Message message =
                          WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT.
                            get(v.getStringValue(), userDNString);
                      logError(message);
                    }
                  }
                }
              }
            }
          }
          else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
          {
            // FIXME -- Is any special processing needed here?
          }
          else
          {
            if (pwPolicyState != null)
            {
              if (saslHandler.isPasswordBased(saslMechanism))
              {
                if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
                {
                  pwPolicyState.updateAuthFailureTimes();
                  if (pwPolicyState.lockedDueToFailures())
                  {
                    AccountStatusNotificationType notificationType;
                    boolean tempLocked;
                    Message message;
                    int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
                    if (lockoutDuration > -1)
                    {
                      notificationType = AccountStatusNotificationType.
                                              ACCOUNT_TEMPORARILY_LOCKED;
                      tempLocked = true;
                      message = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get(
                              secondsToTimeString(lockoutDuration));
                    }
                    else
                    {
                      notificationType = AccountStatusNotificationType.
                                              ACCOUNT_PERMANENTLY_LOCKED;
                      tempLocked = false;
                      message = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
                    }
                    pwPolicyState.generateAccountStatusNotification(
                         notificationType, saslAuthUserEntry, message,
                         AccountStatusNotification.createProperties(
                              pwPolicyState, tempLocked, -1, null, null));
                  }
                }
              }
            }
          }
          break;
        default:
          // Send a protocol error response to the client and disconnect.
          // NYI
          return;
      }
    }
    // Update the user's account with any password policy changes that may be
    // required.
    try
    {
      if (pwPolicyState != null)
      {
        pwPolicyState.updateUserEntry();
      }
    }
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      localOp.setResponseData(de);
    }
    // Invoke the post-operation bind plugins.
    if (! skipPostOperation)
    {
      PostOperationPluginResult postOpResult =
           pluginConfigManager.invokePostOperationBindPlugins(localOp);
      if (postOpResult.connectionTerminated())
      {
        // There's no point in continuing with anything.  Log the result
        // and return.
        localOp.setResultCode(ResultCode.CANCELED);
        localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
        return;
      }
    }
    // Update the authentication information for the user.
    AuthenticationInfo authInfo = localOp.getAuthenticationInfo();
    if ((localOp.getResultCode() == ResultCode.SUCCESS) &&
        (authInfo != null))
    {
      authenticatedUserEntry = authInfo.getAuthenticationEntry();
      clientConnection.setAuthenticationInfo(authInfo);
      clientConnection.setSizeLimit(sizeLimit);
      clientConnection.setTimeLimit(timeLimit);
      clientConnection.setIdleTimeLimit(idleTimeLimit);
      clientConnection.setLookthroughLimit(lookthroughLimit);
      clientConnection.setMustChangePassword(mustChangePassword);
      if (returnAuthzID)
      {
        localOp.addResponseControl(
            new AuthorizationIdentityResponseControl(
                                      authInfo.getAuthorizationDN()));
      }
    }
    // See if we need to send a password policy control to the client.  If so,
    // then add it to the response.
    if (localOp.getResultCode() == ResultCode.SUCCESS)
    {
      if (pwPolicyControlRequested)
      {
        PasswordPolicyResponseControl pwpControl =
             new PasswordPolicyResponseControl(pwPolicyWarningType,
                                               pwPolicyWarningValue,
                                               pwPolicyErrorType);
        localOp.addResponseControl(pwpControl);
      }
      else
      {
        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
        {
          localOp.addResponseControl(new PasswordExpiredControl());
        }
        else if (pwPolicyWarningType ==
                 PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION)
        {
          localOp.addResponseControl(new PasswordExpiringControl(
                                        pwPolicyWarningValue));
        }
      }
    }
    else
    {
      if (pwPolicyControlRequested)
      {
        PasswordPolicyResponseControl pwpControl =
             new PasswordPolicyResponseControl(pwPolicyWarningType,
                                               pwPolicyWarningValue,
                                               pwPolicyErrorType);
        localOp.addResponseControl(pwpControl);
      }
      else
      {
        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
        {
          localOp.addResponseControl(new PasswordExpiredControl());
        }
      }
    }
    // Stop the processing timer.
    localOp.setProcessingStopTime();
  }
  /**
   * Perform an add operation against a local backend.
   *
   * @param operation - The operation to perform
   */
  public void processAdd(AddOperation operation)
  {
    LocalBackendAddOperation localOperation =
      new LocalBackendAddOperation(operation);
    processLocalAdd(localOperation);
  }
  /**
   * Perform a local add operation against a local backend.
   *
   * @param localOp - The operation to perform
   */
  private void processLocalAdd(LocalBackendAddOperation localOp)
  {
    ClientConnection clientConnection = localOp.getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    boolean skipPostOperation = false;
    // Check for 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.
addProcessing:
    {
      // Process the entry DN and set of attributes to convert them from their
      // raw forms as provided by the client to the forms required for the rest
      // of the add processing.
      DN entryDN = localOp.getEntryDN();
      if (entryDN == null){
        break addProcessing;
      }
      Map<ObjectClass, String> objectClasses =
        localOp.getObjectClasses();
      Map<AttributeType, List<Attribute>> userAttributes =
        localOp.getUserAttributes();
      Map<AttributeType, List<Attribute>> operationalAttributes =
        localOp.getOperationalAttributes();
      if ((objectClasses == null ) ||
          (userAttributes == null) ||
          (operationalAttributes == null)){
        break addProcessing;
      }
      // Check for a request to cancel this operation.
      if (localOp.getCancelRequest() != null)
      {
        return;
      }
      // Grab a read lock on the parent entry, if there is one.  We need to do
      // this to ensure that the parent is not deleted or renamed while this add
      // is in progress, and we could also need it to check the entry against
      // a DIT structure rule.
      Lock parentLock = null;
      Lock entryLock  = null;
      DN parentDN = entryDN.getParentDNInSuffix();
      if (parentDN == null)
      {
        // Either this entry is a suffix or doesn't belong in the directory.
        if (DirectoryServer.isNamingContext(entryDN))
        {
          // This is fine.  This entry is one of the configured suffixes.
          parentLock = null;
        }
        else if (entryDN.isNullDN())
        {
          // This is not fine.  The root DSE cannot be added.
          localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          localOp.appendErrorMessage(ERR_ADD_CANNOT_ADD_ROOT_DSE.get());
          break addProcessing;
        }
        else
        {
          // The entry doesn't have a parent but isn't a suffix.  This is not
          // allowed.
          localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
          localOp.appendErrorMessage(ERR_ADD_ENTRY_NOT_SUFFIX.get(
                  String.valueOf(entryDN)));
          break addProcessing;
        }
      }
      else
      {
        for (int i=0; i < 3; i++)
        {
          parentLock = LockManager.lockRead(parentDN);
          if (parentLock != null)
          {
            break;
          }
        }
        if (parentLock == null)
        {
          localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
          localOp.appendErrorMessage(ERR_ADD_CANNOT_LOCK_PARENT.get(
                  String.valueOf(entryDN),
                  String.valueOf(parentDN)));
          skipPostOperation = true;
          break addProcessing;
        }
      }
      try
      {
        // Check for a request to cancel this operation.
        if (localOp.getCancelRequest() != null)
        {
          return;
        }
        // Grab a write lock on the target entry.  We'll need to do this
        // eventually anyway, and we want to make sure that the two locks are
        // always released when exiting this method, no matter what.  Since
        // the entry shouldn't exist yet, locking earlier than necessary
        // shouldn't cause a problem.
        for (int i=0; i < 3; i++)
        {
          entryLock = LockManager.lockWrite(entryDN);
          if (entryLock != null)
          {
            break;
          }
        }
        if (entryLock == null)
        {
          localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
          localOp.appendErrorMessage(ERR_ADD_CANNOT_LOCK_ENTRY.get(
                  String.valueOf(entryDN)));
          skipPostOperation = true;
          break addProcessing;
        }
        // Invoke any conflict resolution processing that might be needed by the
        // synchronization provider.
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            SynchronizationProviderResult result =
                 provider.handleConflictResolution(localOp);
            if (! result.continueOperationProcessing())
            {
              break addProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED.
                get(localOp.getConnectionID(), localOp.getOperationID(),
                    getExceptionMessage(de)));
            localOp.setResponseData(de);
            break addProcessing;
          }
        }
        // If the attribute type is marked "NO-USER-MODIFICATION" then fail
        // unless this is an internal operation or is related to
        // synchronization in some way.
        // This must be done before running the password policy code
        // and any other code that may add attributes marked as
        // "NO-USER-MODIFICATION"
        //
        // Note that doing this checks at this time
        // of the processing does not make it possible for pre-parse plugins
        // to add NO-USER-MODIFICATION attributes to the entry.
        for (AttributeType at : userAttributes.keySet())
        {
          if (at.isNoUserModification())
          {
            if (! (localOp.isInternalOperation() ||
              localOp.isSynchronizationOperation()))
            {
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              localOp.appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
                  String.valueOf(entryDN), at.getNameOrOID()));
              break addProcessing;
            }
          }
        }
        for (AttributeType at : operationalAttributes.keySet())
        {
          if (at.isNoUserModification())
          {
            if (! (localOp.isInternalOperation() ||
              localOp.isSynchronizationOperation()))
            {
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              localOp.appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
                  String.valueOf(entryDN), at.getNameOrOID()));
              break addProcessing;
            }
          }
        }
        // Check to see if the entry already exists.  We do this before
        // checking whether the parent exists to ensure a referral entry
        // above the parent results in a correct referral.
        try
        {
          if (DirectoryServer.entryExists(entryDN))
          {
            localOp.setResultCode(ResultCode.ENTRY_ALREADY_EXISTS);
            localOp.appendErrorMessage(ERR_ADD_ENTRY_ALREADY_EXISTS.get(
                    String.valueOf(entryDN)));
            break addProcessing;
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          localOp.setResultCode(de.getResultCode());
          localOp.appendErrorMessage(de.getMessageObject());
          localOp.setMatchedDN(de.getMatchedDN());
          localOp.setReferralURLs(de.getReferralURLs());
          break addProcessing;
        }
        // Get the parent entry, if it exists.
        Entry parentEntry = null;
        if (parentDN != null)
        {
          try
          {
            parentEntry = DirectoryServer.getEntry(parentDN);
            if (parentEntry == null)
            {
              DN matchedDN = parentDN.getParentDNInSuffix();
              while (matchedDN != null)
              {
                try
                {
                  if (DirectoryServer.entryExists(matchedDN))
                  {
                    localOp.setMatchedDN(matchedDN);
                    break;
                  }
                }
                catch (Exception e)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                  }
                  break;
                }
                matchedDN = matchedDN.getParentDNInSuffix();
              }
              // The parent doesn't exist, so this add can't be successful.
              localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
              localOp.appendErrorMessage(ERR_ADD_NO_PARENT.get(
                      String.valueOf(entryDN),
                      String.valueOf(parentDN)));
              break addProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            localOp.setResultCode(de.getResultCode());
            localOp.appendErrorMessage(de.getMessageObject());
            localOp.setMatchedDN(de.getMatchedDN());
            localOp.setReferralURLs(de.getReferralURLs());
            break addProcessing;
          }
        }
        // Check to make sure that all of the RDN attributes are included as
        // attribute values.  If not, then either add them or report an error.
        RDN rdn = entryDN.getRDN();
        int numAVAs = rdn.getNumValues();
        for (int i=0; i < numAVAs; i++)
        {
          AttributeType  t = rdn.getAttributeType(i);
          AttributeValue v = rdn.getAttributeValue(i);
          String         n = rdn.getAttributeName(i);
          if (t.isOperational())
          {
            List<Attribute> attrList = operationalAttributes.get(t);
            if (attrList == null)
            {
              if (localOp.isSynchronizationOperation() ||
                  DirectoryServer.addMissingRDNAttributes())
              {
                LinkedHashSet<AttributeValue> valueList =
                     new LinkedHashSet<AttributeValue>(1);
                valueList.add(v);
                attrList = new ArrayList<Attribute>();
                attrList.add(new Attribute(t, n, valueList));
                operationalAttributes.put(t, attrList);
              }
              else
              {
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
                        String.valueOf(entryDN),
                        n));
                break addProcessing;
              }
            }
            else
            {
              boolean found = false;
              for (Attribute a : attrList)
              {
                if (a.hasOptions())
                {
                  continue;
                }
                else
                {
                  if (! a.hasValue(v))
                  {
                    a.getValues().add(v);
                  }
                  found = true;
                  break;
                }
              }
              if (! found)
              {
                if (localOp.isSynchronizationOperation() ||
                    DirectoryServer.addMissingRDNAttributes())
                {
                  LinkedHashSet<AttributeValue> valueList =
                       new LinkedHashSet<AttributeValue>(1);
                  valueList.add(v);
                  attrList.add(new Attribute(t, n, valueList));
                }
                else
                {
                  localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  localOp.appendErrorMessage(
                      ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
                              String.valueOf(entryDN), n));
                  break addProcessing;
                }
              }
            }
          }
          else
          {
            List<Attribute> attrList = userAttributes.get(t);
            if (attrList == null)
            {
              if (localOp.isSynchronizationOperation() ||
                  DirectoryServer.addMissingRDNAttributes())
              {
                LinkedHashSet<AttributeValue> valueList =
                     new LinkedHashSet<AttributeValue>(1);
                valueList.add(v);
                attrList = new ArrayList<Attribute>();
                attrList.add(new Attribute(t, n, valueList));
                userAttributes.put(t, attrList);
              }
              else
              {
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                    ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
                            String.valueOf(entryDN),n));
                break addProcessing;
              }
            }
            else
            {
              boolean found = false;
              for (Attribute a : attrList)
              {
                if (a.hasOptions())
                {
                  continue;
                }
                else
                {
                  if (! a.hasValue(v))
                  {
                    a.getValues().add(v);
                  }
                  found = true;
                  break;
                }
              }
              if (! found)
              {
                if (localOp.isSynchronizationOperation() ||
                    DirectoryServer.addMissingRDNAttributes())
                {
                  LinkedHashSet<AttributeValue> valueList =
                       new LinkedHashSet<AttributeValue>(1);
                  valueList.add(v);
                  attrList.add(new Attribute(t, n, valueList));
                }
                else
                {
                  localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  localOp.appendErrorMessage(
                      ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
                              String.valueOf(entryDN),n));
                  break addProcessing;
                }
              }
            }
          }
        }
        // Check to make sure that all objectclasses have their superior classes
        // listed in the entry.  If not, then add them.
        HashSet<ObjectClass> additionalClasses = null;
        for (ObjectClass oc : objectClasses.keySet())
        {
          ObjectClass superiorClass = oc.getSuperiorClass();
          if ((superiorClass != null) &&
              (! objectClasses.containsKey(superiorClass)))
          {
            if (additionalClasses == null)
            {
              additionalClasses = new HashSet<ObjectClass>();
            }
            additionalClasses.add(superiorClass);
          }
        }
        if (additionalClasses != null)
        {
          for (ObjectClass oc : additionalClasses)
          {
            localOp.addObjectClassChain(oc);
          }
        }
        // Create an entry object to encapsulate the set of attributes and
        // objectclasses.
        Entry entry = new Entry(entryDN, objectClasses, userAttributes,
                          operationalAttributes);
        localOp.setEntryToAdd(entry);
        // Check to see if the entry includes a privilege specification.  If so,
        // then the requester must have the PRIVILEGE_CHANGE privilege.
        AttributeType privType =
             DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME, true);
        if (entry.hasAttribute(privType) &&
            (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE,
                localOp)))
        {
          localOp.appendErrorMessage(
                  ERR_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
          localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          break addProcessing;
        }
        // If it's not a synchronization operation, then check
        // to see if the entry contains one or more passwords and if they
        // are valid in accordance with the password policies associated with
        // the user.  Also perform any encoding that might be required by
        // password storage schemes.
        if (! localOp.isSynchronizationOperation())
        {
          // FIXME -- We need to check to see if the password policy subentry
          //          might be specified virtually rather than as a real
          //          attribute.
          PasswordPolicy pwPolicy = null;
          List<Attribute> pwAttrList =
               entry.getAttribute(OP_ATTR_PWPOLICY_POLICY_DN);
          if ((pwAttrList != null) && (! pwAttrList.isEmpty()))
          {
            Attribute a = pwAttrList.get(0);
            LinkedHashSet<AttributeValue> valueSet = a.getValues();
            Iterator<AttributeValue> iterator = valueSet.iterator();
            if (iterator.hasNext())
            {
              DN policyDN;
              try
              {
                policyDN = DN.decode(iterator.next().getValue());
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.appendErrorMessage(
                    ERR_ADD_INVALID_PWPOLICY_DN_SYNTAX.get(
                            String.valueOf(entryDN),
                            de.getMessageObject()));
                localOp.setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
                break addProcessing;
              }
              pwPolicy = DirectoryServer.getPasswordPolicy(policyDN);
              if (pwPolicy == null)
              {
                localOp.appendErrorMessage(
                    ERR_ADD_NO_SUCH_PWPOLICY.get(String.valueOf(entryDN),
                        String.valueOf(policyDN)));
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                break addProcessing;
              }
            }
          }
          if (pwPolicy == null)
          {
            pwPolicy = DirectoryServer.getDefaultPasswordPolicy();
          }
          try
          {
            localOp.handlePasswordPolicy(pwPolicy, entry);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            localOp.setResponseData(de);
            break addProcessing;
          }
        }
        // If the server is configured to check schema and the
        // operation is not a synchronization operation,
        // check to see if the entry is valid according to the server schema,
        // and also whether its attributes are valid according to their syntax.
        if ((DirectoryServer.checkSchema()) &&
            (!localOp.isSynchronizationOperation()) )
        {
          MessageBuilder invalidReason = new MessageBuilder();
          if (! entry.conformsToSchema(parentEntry, true, true, true,
                                       invalidReason))
          {
            localOp.setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
            localOp.setErrorMessage(invalidReason);
            break addProcessing;
          }
          else
          {
            switch (DirectoryServer.getSyntaxEnforcementPolicy())
            {
              case REJECT:
                invalidReason = new MessageBuilder();
                for (List<Attribute> attrList : userAttributes.values())
                {
                  for (Attribute a : attrList)
                  {
                    AttributeSyntax syntax = a.getAttributeType().getSyntax();
                    if (syntax != null)
                    {
                      for (AttributeValue v : a.getValues())
                      {
                        if (! syntax.valueIsAcceptable(v.getValue(),
                                                       invalidReason))
                        {
                          Message message = WARN_ADD_OP_INVALID_SYNTAX.
                              get(String.valueOf(entryDN),
                                  String.valueOf(v.getStringValue()),
                                  String.valueOf(a.getName()),
                                  String.valueOf(invalidReason));
                          invalidReason = new MessageBuilder(message);
                          localOp.setResultCode(
                              ResultCode.INVALID_ATTRIBUTE_SYNTAX);
                          localOp.setErrorMessage(invalidReason);
                          break addProcessing;
                        }
                      }
                    }
                  }
                }
                for (List<Attribute> attrList :
                     operationalAttributes.values())
                {
                  for (Attribute a : attrList)
                  {
                    AttributeSyntax syntax = a.getAttributeType().getSyntax();
                    if (syntax != null)
                    {
                      for (AttributeValue v : a.getValues())
                      {
                        if (! syntax.valueIsAcceptable(v.getValue(),
                                                       invalidReason))
                        {
                          Message message = WARN_ADD_OP_INVALID_SYNTAX.
                              get(String.valueOf(entryDN),
                                  String.valueOf(v.getStringValue()),
                                  String.valueOf(a.getName()),
                                  String.valueOf(invalidReason));
                          invalidReason = new MessageBuilder(message);
                          localOp.setResultCode(
                              ResultCode.INVALID_ATTRIBUTE_SYNTAX);
                          localOp.setErrorMessage(invalidReason);
                          break addProcessing;
                        }
                      }
                    }
                  }
                }
                break;
              case WARN:
                invalidReason = new MessageBuilder();
                for (List<Attribute> attrList : userAttributes.values())
                {
                  for (Attribute a : attrList)
                  {
                    AttributeSyntax syntax = a.getAttributeType().getSyntax();
                    if (syntax != null)
                    {
                      for (AttributeValue v : a.getValues())
                      {
                        if (! syntax.valueIsAcceptable(v.getValue(),
                                                       invalidReason))
                        {
                          logError(WARN_ADD_OP_INVALID_SYNTAX.
                              get(String.valueOf(entryDN),
                                  String.valueOf(v.getStringValue()),
                                  String.valueOf(a.getName()),
                                  String.valueOf(invalidReason)));
                        }
                      }
                    }
                  }
                }
                for (List<Attribute> attrList : operationalAttributes.values())
                {
                  for (Attribute a : attrList)
                  {
                    AttributeSyntax syntax = a.getAttributeType().getSyntax();
                    if (syntax != null)
                    {
                      for (AttributeValue v : a.getValues())
                      {
                        if (! syntax.valueIsAcceptable(v.getValue(),
                                                       invalidReason))
                        {
                          logError(WARN_ADD_OP_INVALID_SYNTAX.
                              get(String.valueOf(entryDN),
                                  String.valueOf(v.getStringValue()),
                                  String.valueOf(a.getName()),
                                  String.valueOf(invalidReason)));
                        }
                      }
                    }
                  }
                }
                break;
            }
          }
          // See if the entry contains any attributes or object classes marked
          // OBSOLETE.  If so, then reject the entry.
          for (AttributeType at : userAttributes.keySet())
          {
            if (at.isObsolete())
            {
              Message message = WARN_ADD_ATTR_IS_OBSOLETE.get(
                      String.valueOf(entryDN),
                      at.getNameOrOID());
              localOp.appendErrorMessage(message);
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              break addProcessing;
            }
          }
          for (AttributeType at : operationalAttributes.keySet())
          {
            if (at.isObsolete())
            {
              Message message = WARN_ADD_ATTR_IS_OBSOLETE.get(
                      String.valueOf(entryDN),
                      at.getNameOrOID());
              localOp.appendErrorMessage(message);
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              break addProcessing;
            }
          }
          for (ObjectClass oc : objectClasses.keySet())
          {
            if (oc.isObsolete())
            {
              Message message = WARN_ADD_OC_IS_OBSOLETE.get(
                      String.valueOf(entryDN),
                      oc.getNameOrOID());
              localOp.appendErrorMessage(message);
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              break addProcessing;
            }
          }
        }
        // Check to see if there are any controls in the request. If so, then
        // see if there is any special processing required.
        boolean                    noOp            = false;
        LDAPPostReadRequestControl postReadRequest = null;
        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 (!AccessControlConfigManager.getInstance().
                    getAccessControlHandler().isAllowed(parentDN, localOp, c))
            {
              localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
              localOp.appendErrorMessage(
                      ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
              skipPostOperation = true;
              break addProcessing;
            }
            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.getMessageObject());
                  break addProcessing;
                }
              }
              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(
                          ERR_ADD_ASSERTION_FAILED.get(
                                  String.valueOf(entryDN)));
                  break addProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
                localOp.appendErrorMessage(
                        ERR_ADD_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
                break addProcessing;
              }
            }
            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
            {
              noOp = true;
            }
            else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
            {
              if (c instanceof LDAPPostReadRequestControl)
              {
                postReadRequest = (LDAPPostReadRequestControl) c;
              }
              else
              {
                try
                {
                  postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
                  requestControls.set(i, postReadRequest);
                }
                catch (LDAPException le)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
                  }
                  localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
                  localOp.appendErrorMessage(le.getMessageObject());
                  break addProcessing;
                }
              }
            }
            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))
              {
                localOp.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
                break addProcessing;
              }
              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.getMessageObject());
                  break addProcessing;
                }
              }
              Entry authorizationEntry;
              try
              {
                authorizationEntry = proxyControl.getAuthorizationEntry();
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(de.getResultCode());
                localOp.appendErrorMessage(de.getMessageObject());
                break addProcessing;
              }
              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))
              {
                localOp.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
                break addProcessing;
              }
              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.getMessageObject());
                  break addProcessing;
                }
              }
              Entry authorizationEntry;
              try
              {
                authorizationEntry = proxyControl.getAuthorizationEntry();
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(de.getResultCode());
                localOp.appendErrorMessage(de.getMessageObject());
                break addProcessing;
              }
              localOp.setAuthorizationEntry(authorizationEntry);
              if (authorizationEntry == null)
              {
                localOp.setProxiedAuthorizationDN(DN.nullDN());
              }
              else
              {
                localOp.setProxiedAuthorizationDN(authorizationEntry.getDN());
              }
            }
            else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
            {
              // We don't need to do anything here because it's already handled
              // in LocalBackendAddOperation.handlePasswordPolicy().
            }
            // NYI -- Add support for additional controls.
            else if (c.isCritical())
            {
              if ((backend == null) || (! backend.supportsControl(oid)))
              {
                localOp.setResultCode(
                    ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
                localOp.appendErrorMessage(
                        ERR_ADD_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN),
                                oid));
                break addProcessing;
              }
            }
          }
        }
        // Check to see if the client has permission to perform the add.
        // 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 or
        // if the parent entry does not exist may have already exposed
        // sensitive information to the client.
        if (AccessControlConfigManager.getInstance()
            .getAccessControlHandler().isAllowed(localOp) == false) {
          localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          localOp.appendErrorMessage(ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS
                  .get(String.valueOf(entryDN)));
          skipPostOperation = true;
          break addProcessing;
        }
        // Check for a request to cancel this operation.
        if (localOp.getCancelRequest() != null)
        {
          return;
        }
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation add plugins.
        if (!localOp.isSynchronizationOperation())
        {
          PreOperationPluginResult preOpResult =
            pluginConfigManager.invokePreOperationAddPlugins(localOp);
          if (preOpResult.connectionTerminated())
          {
            // There's no point in continuing with anything.  Log the result
            // and return.
            localOp.setResultCode(ResultCode.CANCELED);
            localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
            return;
          }
          else if (preOpResult.sendResponseImmediately())
          {
            skipPostOperation = true;
            break addProcessing;
          }
          else if (preOpResult.skipCoreProcessing())
          {
            skipPostOperation = false;
            break addProcessing;
          }
        }
        // Check for a request to cancel this operation.
        if (localOp.getCancelRequest() != null)
        {
          return;
        }
        // Actually perform the add operation.  This should also include taking
        // care of any synchronization that might be needed.
        Backend backend = DirectoryServer.getBackend(entryDN);
        if (backend == null)
        {
          localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
          localOp.appendErrorMessage(Message.raw("No backend for entry " +
              entryDN.toString())); // TODO: i18n
        }
        else
        {
          // If it is not a private backend, then check to see if the server or
          // backend is operating in read-only mode.
          if (! backend.isPrivateBackend())
          {
            switch (DirectoryServer.getWritabilityMode())
            {
              case DISABLED:
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
                        String.valueOf(entryDN)));
                break addProcessing;
              case INTERNAL_ONLY:
                if (! (localOp.isInternalOperation() ||
                    localOp.isSynchronizationOperation()))
                {
                  localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  localOp.appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
                          String.valueOf(entryDN)));
                  break addProcessing;
                }
            }
            switch (backend.getWritabilityMode())
            {
              case DISABLED:
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
                        String.valueOf(entryDN)));
                break addProcessing;
              case INTERNAL_ONLY:
                if (! (localOp.isInternalOperation() ||
                    localOp.isSynchronizationOperation()))
                {
                  localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  localOp.appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
                          String.valueOf(entryDN)));
                  break addProcessing;
                }
            }
          }
          try
          {
            if (noOp)
            {
              localOp.appendErrorMessage(INFO_ADD_NOOP.get());
              localOp.setResultCode(ResultCode.NO_OPERATION);
            }
            else
            {
              for (SynchronizationProvider provider :
                   DirectoryServer.getSynchronizationProviders())
              {
                try
                {
                  SynchronizationProviderResult result =
                       provider.doPreOperation(localOp);
                  if (! result.continueOperationProcessing())
                  {
                    break addProcessing;
                  }
                }
                catch (DirectoryException de)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
                  }
                  logError(ERR_ADD_SYNCH_PREOP_FAILED.
                      get(localOp.getConnectionID(), localOp.getOperationID(),
                          getExceptionMessage(de)));
                  localOp.setResponseData(de);
                  break addProcessing;
                }
              }
              backend.addEntry(entry, localOp);
            }
            if (postReadRequest != null)
            {
              Entry addedEntry = entry.duplicate(true);
              if (! postReadRequest.allowsAttribute(
                         DirectoryServer.getObjectClassAttributeType()))
              {
                addedEntry.removeAttribute(
                     DirectoryServer.getObjectClassAttributeType());
              }
              if (! postReadRequest.returnAllUserAttributes())
              {
                Iterator<AttributeType> iterator =
                     addedEntry.getUserAttributes().keySet().iterator();
                while (iterator.hasNext())
                {
                  AttributeType attrType = iterator.next();
                  if (! postReadRequest.allowsAttribute(attrType))
                  {
                    iterator.remove();
                  }
                }
              }
              if (! postReadRequest.returnAllOperationalAttributes())
              {
                Iterator<AttributeType> iterator =
                     addedEntry.getOperationalAttributes().keySet().iterator();
                while (iterator.hasNext())
                {
                  AttributeType attrType = iterator.next();
                  if (! postReadRequest.allowsAttribute(attrType))
                  {
                    iterator.remove();
                  }
                }
              }
              // FIXME -- Check access controls on the entry to see if it should
              //          be returned or if any attributes need to be stripped
              //          out..
              SearchResultEntry searchEntry = new SearchResultEntry(addedEntry);
              LDAPPostReadResponseControl responseControl =
                   new LDAPPostReadResponseControl(postReadRequest.getOID(),
                                                   postReadRequest.isCritical(),
                                                   searchEntry);
              localOp.addResponseControl(responseControl);
            }
            if (! noOp)
            {
              localOp.setResultCode(ResultCode.SUCCESS);
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            localOp.setResultCode(de.getResultCode());
            localOp.appendErrorMessage(de.getMessageObject());
            localOp.setMatchedDN(de.getMatchedDN());
            localOp.setReferralURLs(de.getReferralURLs());
            break addProcessing;
          }
          catch (CancelledOperationException coe)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, coe);
            }
            CancelResult cancelResult = coe.getCancelResult();
            localOp.setCancelResult(cancelResult);
            localOp.setResultCode(cancelResult.getResultCode());
            Message message = coe.getMessageObject();
            if ((message != null) && (message.length() > 0))
            {
              localOp.appendErrorMessage(message);
            }
            break addProcessing;
          }
        }
      }
      finally
      {
        if (entryLock != null)
        {
          LockManager.unlock(entryDN, entryLock);
        }
        if (parentLock != null)
        {
          LockManager.unlock(parentDN, parentLock);
        }
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.doPostOperation(localOp);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_ADD_SYNCH_POSTOP_FAILED.
                get(localOp.getConnectionID(), localOp.getOperationID(),
                    getExceptionMessage(de)));
            localOp.setResponseData(de);
            break;
          }
        }
      }
    }
    // Indicate that it is now too late to attempt to cancel the operation.
    localOp.setCancelResult(CancelResult.TOO_LATE);
    // Invoke the post-operation or post-synchronization add plugins.
    if (localOp.isSynchronizationOperation())
    {
      if (localOp.getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationAddPlugins(localOp);
      }
    }
    else if (! skipPostOperation)
    {
      // FIXME -- Should this also be done while holding the locks?
      PostOperationPluginResult postOpResult =
           pluginConfigManager.invokePostOperationAddPlugins(localOp);
      if (postOpResult.connectionTerminated())
      {
        // There's no point in continuing with anything.  Log the result and
        // return.
        localOp.setResultCode(ResultCode.CANCELED);
        localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
        return;
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    if ((localOp.getResultCode() == ResultCode.SUCCESS) &&
        (localOp.getEntryToAdd() != null))
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleAddOperation(localOp, localOp.getEntryToAdd());
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          Message message = ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
          logError(message);
        }
      }
    }
  }
  /**
   * Performs a delete operation against a local backend.
   *
   * @param operation the operation to perform
   */
  public void processDelete(DeleteOperation operation){
    LocalBackendDeleteOperation localOperation =
      new LocalBackendDeleteOperation(operation);
    processLocalDelete(localOperation);
  }
  /**
   * Performs a local delete operation against a local backend.
   *
   * @param localOp the operation to perform
   */
  private void processLocalDelete(LocalBackendDeleteOperation localOp)
  {
    ClientConnection clientConnection = localOp.getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    boolean skipPostOperation = false;
    // Check for 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.
deleteProcessing:
    {
      // Process the entry DN to convert it from its raw form as provided by the
      // client to the form required for the rest of the delete processing.
      DN entryDN = localOp.getEntryDN();
      if (entryDN == null){
        break deleteProcessing;
      }
      // Grab a write lock on the entry.
      Lock entryLock = null;
      for (int i=0; i < 3; i++)
      {
        entryLock = LockManager.lockWrite(entryDN);
        if (entryLock != null)
        {
          break;
        }
      }
      if (entryLock == null)
      {
        localOp.setResultCode(DirectoryServer.getServerErrorResultCode());
        localOp.appendErrorMessage(ERR_DELETE_CANNOT_LOCK_ENTRY.get(
                String.valueOf(entryDN)));
        break deleteProcessing;
      }
      Entry entry = null;
      try
      {
        // Get the entry to delete.  If it doesn't exist, then fail.
        try
        {
          entry = backend.getEntry(entryDN);
          localOp.setEntryToDelete(entry);
          if (entry == null)
          {
            localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
            localOp.appendErrorMessage(ERR_DELETE_NO_SUCH_ENTRY.get(
                    String.valueOf(entryDN)));
            try
            {
              DN parentDN = entryDN.getParentDNInSuffix();
              while (parentDN != null)
              {
                if (DirectoryServer.entryExists(parentDN))
                {
                  localOp.setMatchedDN(parentDN);
                  break;
                }
                parentDN = parentDN.getParentDNInSuffix();
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
            break deleteProcessing;
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          localOp.setResultCode(de.getResultCode());
          localOp.appendErrorMessage(de.getMessageObject());
          localOp.setMatchedDN(de.getMatchedDN());
          localOp.setReferralURLs(de.getReferralURLs());
          break deleteProcessing;
        }
        // Invoke any conflict resolution processing that might be needed by the
        // synchronization provider.
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            SynchronizationProviderResult result =
                 provider.handleConflictResolution(localOp);
            if (! result.continueOperationProcessing())
            {
              break deleteProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_DELETE_SYNCH_CONFLICT_RESOLUTION_FAILED.
                get(localOp.getConnectionID(), localOp.getOperationID(),
                    getExceptionMessage(de)));
            localOp.setResponseData(de);
            break deleteProcessing;
          }
        }
        // Check to see if the client has permission to perform the
        // delete.
        // Check to see if there are any controls in the request.  If so, then
        // see if there is any special processing required.
        boolean                   noOp           = false;
        LDAPPreReadRequestControl preReadRequest = null;
        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 (!AccessControlConfigManager.getInstance().
                     getAccessControlHandler().isAllowed(entryDN, localOp, c))
            {
              localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
              localOp.appendErrorMessage(
                      ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
              skipPostOperation = true;
              break deleteProcessing;
            }
            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.getMessageObject());
                  break deleteProcessing;
                }
              }
              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(
                          ERR_DELETE_ASSERTION_FAILED.get(
                                  String.valueOf(entryDN)));
                  break deleteProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
                localOp.appendErrorMessage(
                        ERR_DELETE_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
                break deleteProcessing;
              }
            }
            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
            {
              noOp = true;
            }
            else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
            {
              if (c instanceof LDAPPreReadRequestControl)
              {
                preReadRequest = (LDAPPreReadRequestControl) c;
              }
              else
              {
                try
                {
                  preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
                  requestControls.set(i, preReadRequest);
                }
                catch (LDAPException le)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
                  }
                  localOp.setResultCode(ResultCode.valueOf(le.getResultCode()));
                  localOp.appendErrorMessage(le.getMessageObject());
                  break deleteProcessing;
                }
              }
            }
            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))
              {
                localOp.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
                break deleteProcessing;
              }
              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.getMessageObject());
                  break deleteProcessing;
                }
              }
              Entry authorizationEntry;
              try
              {
                authorizationEntry = proxyControl.getAuthorizationEntry();
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(de.getResultCode());
                localOp.appendErrorMessage(de.getMessageObject());
                break deleteProcessing;
              }
              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))
              {
                localOp.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                localOp.setResultCode(ResultCode.AUTHORIZATION_DENIED);
                break deleteProcessing;
              }
              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.getMessageObject());
                  break deleteProcessing;
                }
              }
              Entry authorizationEntry;
              try
              {
                authorizationEntry = proxyControl.getAuthorizationEntry();
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(de.getResultCode());
                localOp.appendErrorMessage(de.getMessageObject());
                break deleteProcessing;
              }
              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);
                localOp.appendErrorMessage(
                        ERR_DELETE_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN),
                                oid));
                break deleteProcessing;
              }
            }
          }
        }
        // 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);
          localOp.appendErrorMessage(
                  ERR_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                          String.valueOf(entryDN)));
          skipPostOperation = true;
          break deleteProcessing;
        }
        // Check for a request to cancel this operation.
        if (localOp.getCancelRequest() != null)
        {
          return;
        }
        // If the operation is not a synchronization operation,
        // invoke the pre-delete plugins.
        if (!localOp.isSynchronizationOperation())
        {
          PreOperationPluginResult preOpResult =
            pluginConfigManager.invokePreOperationDeletePlugins(localOp);
          if (preOpResult.connectionTerminated())
          {
            // There's no point in continuing with anything.  Log the request
            // and result and return.
            localOp.setResultCode(ResultCode.CANCELED);
            localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
            localOp.setProcessingStopTime();
            return;
          }
          else if (preOpResult.sendResponseImmediately())
          {
            skipPostOperation = true;
            break deleteProcessing;
          }
          else if (preOpResult.skipCoreProcessing())
          {
            skipPostOperation = false;
            break deleteProcessing;
          }
        }
        // Check for a request to cancel this operation.
        if (localOp.getCancelRequest() != null)
        {
          return;
        }
        // Get the backend to use for the delete.  If there is none, then fail.
        if (backend == null)
        {
          localOp.setResultCode(ResultCode.NO_SUCH_OBJECT);
          localOp.appendErrorMessage(
                  ERR_DELETE_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
          break deleteProcessing;
        }
        // If it is not a private backend, then check to see if the server or
        // backend is operating in read-only mode.
        if (! backend.isPrivateBackend())
        {
          switch (DirectoryServer.getWritabilityMode())
          {
            case DISABLED:
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              localOp.appendErrorMessage(
                      ERR_DELETE_SERVER_READONLY.get(String.valueOf(entryDN)));
              break deleteProcessing;
            case INTERNAL_ONLY:
              if (! (localOp.isInternalOperation() ||
                  localOp.isSynchronizationOperation()))
              {
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                        ERR_DELETE_SERVER_READONLY.get(
                                String.valueOf(entryDN)));
                break deleteProcessing;
              }
          }
          switch (backend.getWritabilityMode())
          {
            case DISABLED:
              localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              localOp.appendErrorMessage(
                      ERR_DELETE_BACKEND_READONLY.get(String.valueOf(entryDN)));
              break deleteProcessing;
            case INTERNAL_ONLY:
              if (! (localOp.isInternalOperation() ||
                  localOp.isSynchronizationOperation()))
              {
                localOp.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                localOp.appendErrorMessage(
                        ERR_DELETE_BACKEND_READONLY.get(
                                String.valueOf(entryDN)));
                break deleteProcessing;
              }
          }
        }
        // The selected backend will have the responsibility of making sure that
        // the entry actually exists and does not have any children (or possibly
        // handling a subtree delete).  But we will need to check if there are
        // any subordinate backends that should stop us from attempting the
        // delete.
        Backend[] subBackends = backend.getSubordinateBackends();
        for (Backend b : subBackends)
        {
          DN[] baseDNs = b.getBaseDNs();
          for (DN dn : baseDNs)
          {
            if (dn.isDescendantOf(entryDN))
            {
              localOp.setResultCode(ResultCode.NOT_ALLOWED_ON_NONLEAF);
              localOp.appendErrorMessage(ERR_DELETE_HAS_SUB_BACKEND.get(
                      String.valueOf(entryDN),
                      String.valueOf(dn)));
              break deleteProcessing;
            }
          }
        }
        // Actually perform the delete.
        try
        {
          if (noOp)
          {
            localOp.appendErrorMessage(INFO_DELETE_NOOP.get());
            localOp.setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
            for (SynchronizationProvider provider :
                 DirectoryServer.getSynchronizationProviders())
            {
              try
              {
                SynchronizationProviderResult result =
                     provider.doPreOperation(localOp);
                if (! result.continueOperationProcessing())
                {
                  break deleteProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                logError(ERR_DELETE_SYNCH_PREOP_FAILED.
                    get(localOp.getConnectionID(), localOp.getOperationID(),
                        getExceptionMessage(de)));
                localOp.setResponseData(de);
                break deleteProcessing;
              }
            }
            backend.deleteEntry(entryDN, localOp);
          }
          if (preReadRequest != null)
          {
            Entry entryCopy = entry.duplicate(true);
            if (! preReadRequest.allowsAttribute(
                       DirectoryServer.getObjectClassAttributeType()))
            {
              entryCopy.removeAttribute(
                   DirectoryServer.getObjectClassAttributeType());
            }
            if (! preReadRequest.returnAllUserAttributes())
            {
              Iterator<AttributeType> iterator =
                   entryCopy.getUserAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! preReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            if (! preReadRequest.returnAllOperationalAttributes())
            {
              Iterator<AttributeType> iterator =
                   entryCopy.getOperationalAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! preReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            // FIXME -- Check access controls on the entry to see if it should
            //          be returned or if any attributes need to be stripped
            //          out..
            SearchResultEntry searchEntry = new SearchResultEntry(entryCopy);
            LDAPPreReadResponseControl responseControl =
                 new LDAPPreReadResponseControl(preReadRequest.getOID(),
                                                preReadRequest.isCritical(),
                                                searchEntry);
            localOp.addResponseControl(responseControl);
          }
          if (! noOp)
          {
            localOp.setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          localOp.setResultCode(de.getResultCode());
          localOp.appendErrorMessage(de.getMessageObject());
          localOp.setMatchedDN(de.getMatchedDN());
          localOp.setReferralURLs(de.getReferralURLs());
          break deleteProcessing;
        }
        catch (CancelledOperationException coe)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
          }
          CancelResult cancelResult = coe.getCancelResult();
          localOp.setCancelResult(cancelResult);
          localOp.setResultCode(cancelResult.getResultCode());
          Message message = coe.getMessageObject();
          if ((message != null) && (message.length() > 0))
          {
            localOp.appendErrorMessage(message);
          }
          break deleteProcessing;
        }
      }
      finally
      {
        LockManager.unlock(entryDN, entryLock);
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.doPostOperation(localOp);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_DELETE_SYNCH_POSTOP_FAILED.
                get(localOp.getConnectionID(), localOp.getOperationID(),
                    getExceptionMessage(de)));
            localOp.setResponseData(de);
            break;
          }
        }
      }
    }
    // Indicate that it is now too late to attempt to cancel the operation.
    localOp.setCancelResult(CancelResult.TOO_LATE);
    // Invoke the post-operation or post-synchronization delete plugins.
    if (localOp.isSynchronizationOperation())
    {
      if (localOp.getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationDeletePlugins(localOp);
      }
    }
    else if (! skipPostOperation)
    {
      PostOperationPluginResult postOperationResult =
           pluginConfigManager.invokePostOperationDeletePlugins(localOp);
      if (postOperationResult.connectionTerminated())
      {
        localOp.setResultCode(ResultCode.CANCELED);
        localOp.appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
        localOp.setProcessingStopTime();
        return;
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    if (localOp.getResultCode() == ResultCode.SUCCESS)
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleDeleteOperation(localOp,
              localOp.getEntryToDelete());
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          Message message = ERR_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
          logError(message);
        }
      }
    }
    // Stop the processing timer.
    localOp.setProcessingStopTime();
  }
  /**
   * 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 localOp - 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 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)))
      {
        localOp.appendErrorMessage(
                ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get());
        localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        skipPostOperation = true;
        break compareProcessing;
      }
      // Check for 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)
      {
        Message message = ERR_COMPARE_CANNOT_LOCK_ENTRY.get(
                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(
                    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))
                {
                  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.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.
        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 (!AccessControlConfigManager.getInstance().
                    getAccessControlHandler().
                    isAllowed(entryDN, localOp, c))
            {
              localOp.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
              localOp.appendErrorMessage(
                      ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
              skipPostOperation = true;
              break compareProcessing;
            }
            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.getMessageObject());
                  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(
                      ERR_COMPARE_ASSERTION_FAILED.get(
                              String.valueOf(entryDN)));
                  break compareProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                localOp.setResultCode(ResultCode.PROTOCOL_ERROR);
                localOp.appendErrorMessage(
                    ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(
                            String.valueOf(entryDN),
                            de.getMessageObject()));
                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))
              {
                localOp.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                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.getMessageObject());
                  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.getMessageObject());
                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))
              {
                localOp.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                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.getMessageObject());
                  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.getMessageObject());
                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);
                localOp.appendErrorMessage(
                    ERR_COMPARE_UNSUPPORTED_CRITICAL_CONTROL.get(
                            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);
          localOp.appendErrorMessage(
              ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                      String.valueOf(entryDN)));
          skipPostOperation = true;
          break compareProcessing;
        }
        // Check for 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);
          localOp.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
          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(WARN_COMPARE_OP_NO_SUCH_ATTR.get(
                    String.valueOf(entryDN), baseName));
          }
          else
          {
            localOp.appendErrorMessage(
                    WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS.get(
                            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 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);
        localOp.appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
        return;
      }
    }
  }
  /**
   * Perform a moddn operation against a local backend.
   *
   * @param op The operation to perform
   */
  public void processModifyDN(ModifyDNOperation op)
  {
    LocalBackendModifyDNOperation localOp =
      new LocalBackendModifyDNOperation(op);
    processLocalModifyDN(localOp);
  }
  /**
   * Perform a local moddn operation against the local backend.
   *
   * @param op - The operation to perform
   */
  private void processLocalModifyDN(LocalBackendModifyDNOperation op)
  {
    ClientConnection clientConnection = op.getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    boolean skipPostOperation = false;
    // Check for a request to cancel this operation.
    if (op.getCancelRequest() != null)
    {
      return;
    }
    // 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.
      DN entryDN = op.getEntryDN();
      RDN newRDN = op.getNewRDN();
      if (newRDN == null)
      {
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      DN newSuperior = op.getNewSuperior();
      if ((newSuperior == null) &&
          (op.getRawNewSuperior() != null))
      {
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      // Construct the new DN to use for the entry.
      DN parentDN;
      if (newSuperior == null)
      {
        parentDN = entryDN.getParentDNInSuffix();
      }
      else
      {
        parentDN = newSuperior;
      }
      if ((parentDN == null) || parentDN.isNullDN())
      {
        op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        op.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)
      {
        op.setResultCode(ResultCode.NO_SUCH_OBJECT);
        op.appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get(
                String.valueOf(entryDN)));
        break modifyDNProcessing;
      }
      Backend newBackend = DirectoryServer.getBackend(newDN);
      if (newBackend == null)
      {
        op.setResultCode(ResultCode.NO_SUCH_OBJECT);
        op.appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get(
                String.valueOf(entryDN),
                                      String.valueOf(newDN)));
        break modifyDNProcessing;
      }
      else if (! currentBackend.equals(newBackend))
      {
        op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        op.appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get(
                String.valueOf(entryDN),
                String.valueOf(newDN)));
        break modifyDNProcessing;
      }
      // Check for a request to cancel this operation.
      if (op.getCancelRequest() != null)
      {
        return;
      }
      // Acquire write locks for the current and new DN.
      Lock currentLock = null;
      for (int i=0; i < 3; i++)
      {
        currentLock = LockManager.lockWrite(entryDN);
        if (currentLock != null)
        {
          break;
        }
      }
      if (currentLock == null)
      {
        op.setResultCode(DirectoryServer.getServerErrorResultCode());
        op.appendErrorMessage(ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get(
                String.valueOf(entryDN)));
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      Lock newLock = null;
      try
      {
        for (int i=0; i < 3; i++)
        {
          newLock = LockManager.lockWrite(newDN);
          if (newLock != null)
          {
            break;
          }
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        LockManager.unlock(entryDN, currentLock);
        if (newLock != null)
        {
          LockManager.unlock(newDN, newLock);
        }
        op.setResultCode(DirectoryServer.getServerErrorResultCode());
        op.appendErrorMessage(ERR_MODDN_EXCEPTION_LOCKING_NEW_DN.get(
                String.valueOf(entryDN),
                String.valueOf(newDN),
                getExceptionMessage(e)));
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      if (newLock == null)
      {
        LockManager.unlock(entryDN, currentLock);
        op.setResultCode(DirectoryServer.getServerErrorResultCode());
        op.appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(
                String.valueOf(entryDN),
                String.valueOf(newDN)));
        skipPostOperation = true;
        break modifyDNProcessing;
      }
      Entry currentEntry = null;
      try
      {
        // Check for a request to cancel this operation.
        if (op.getCancelRequest() != null)
        {
          return;
        }
        // Get the current entry from the appropriate backend.  If it doesn't
        // exist, then fail.
        try
        {
          currentEntry = currentBackend.getEntry(entryDN);
          op.setOriginalEntry(currentEntry);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          op.setResultCode(de.getResultCode());
          op.appendErrorMessage(de.getMessageObject());
          op.setMatchedDN(de.getMatchedDN());
          op.setReferralURLs(de.getReferralURLs());
          break modifyDNProcessing;
        }
        if (op.getOriginalEntry() == null)
        {
          // See if one of the entry's ancestors exists.
          parentDN = entryDN.getParentDNInSuffix();
          while (parentDN != null)
          {
            try
            {
              if (DirectoryServer.entryExists(parentDN))
              {
                op.setMatchedDN(parentDN);
                break;
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              break;
            }
            parentDN = parentDN.getParentDNInSuffix();
          }
          op.setResultCode(ResultCode.NO_SUCH_OBJECT);
          op.appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get(
                  String.valueOf(entryDN)));
          break modifyDNProcessing;
        }
        // Invoke any conflict resolution processing that might be needed by the
        // synchronization provider.
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            SynchronizationProviderResult result =
                 provider.handleConflictResolution(op);
            if (! result.continueOperationProcessing())
            {
              break modifyDNProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_MODDN_SYNCH_CONFLICT_RESOLUTION_FAILED.
                get(op.getConnectionID(), op.getOperationID(),
                    getExceptionMessage(de)));
            op.setResponseData(de);
            break modifyDNProcessing;
          }
        }
        // Check to see if there are any controls in the request.  If so, then
        // see if there is any special processing required.
        boolean                    noOp            = false;
        LDAPPreReadRequestControl  preReadRequest  = null;
        LDAPPostReadRequestControl postReadRequest = null;
        List<Control> requestControls = op.getRequestControls();
        if ((requestControls != null) && (! requestControls.isEmpty()))
        {
          for (int i=0; i < requestControls.size(); i++)
          {
            Control c   = requestControls.get(i);
            String  oid = c.getOID();
            if (!AccessControlConfigManager.getInstance().
                     getAccessControlHandler().isAllowed(entryDN,  op, c))
            {
              op.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
              op.appendErrorMessage(
                      ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
              skipPostOperation = true;
              break modifyDNProcessing;
            }
            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);
                  }
                  op.setResultCode(ResultCode.valueOf(le.getResultCode()));
                  op.appendErrorMessage(le.getMessageObject());
                  break modifyDNProcessing;
                }
              }
              try
              {
                // FIXME -- We need to determine whether the current user has
                //          permission to make this determination.
                SearchFilter filter = assertControl.getSearchFilter();
                if (! filter.matchesEntry(currentEntry))
                {
                  op.setResultCode(ResultCode.ASSERTION_FAILED);
                  op.appendErrorMessage(ERR_MODDN_ASSERTION_FAILED.get(
                          String.valueOf(entryDN)));
                  break modifyDNProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                op.setResultCode(ResultCode.PROTOCOL_ERROR);
                op.appendErrorMessage(
                        ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                String.valueOf(entryDN),
                                de.getMessageObject()));
                break modifyDNProcessing;
              }
            }
            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
            {
              noOp = true;
            }
            else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
            {
              if (c instanceof LDAPPreReadRequestControl)
              {
                preReadRequest = (LDAPPreReadRequestControl) c;
              }
              else
              {
                try
                {
                  preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
                  requestControls.set(i, preReadRequest);
                }
                catch (LDAPException le)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
                  }
                  op.setResultCode(ResultCode.valueOf(le.getResultCode()));
                  op.appendErrorMessage(le.getMessageObject());
                  break modifyDNProcessing;
                }
              }
            }
            else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
            {
              if (c instanceof LDAPPostReadRequestControl)
              {
                postReadRequest = (LDAPPostReadRequestControl) c;
              }
              else
              {
                try
                {
                  postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
                  requestControls.set(i, postReadRequest);
                }
                catch (LDAPException le)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
                  }
                  op.setResultCode(ResultCode.valueOf(le.getResultCode()));
                  op.appendErrorMessage(le.getMessageObject());
                  break modifyDNProcessing;
                }
              }
            }
            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, op))
              {
                op.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                op.setResultCode(ResultCode.AUTHORIZATION_DENIED);
                break modifyDNProcessing;
              }
              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);
                  }
                  op.setResultCode(ResultCode.valueOf(le.getResultCode()));
                  op.appendErrorMessage(le.getMessageObject());
                  break modifyDNProcessing;
                }
              }
              Entry authorizationEntry;
              try
              {
                authorizationEntry = proxyControl.getAuthorizationEntry();
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                op.setResultCode(de.getResultCode());
                op.appendErrorMessage(de.getMessageObject());
                break modifyDNProcessing;
              }
              op.setAuthorizationEntry(authorizationEntry);
              if (authorizationEntry == null)
              {
                op.setProxiedAuthorizationDN(DN.nullDN());
              }
              else
              {
                op.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, op))
              {
                op.appendErrorMessage(
                        ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
                op.setResultCode(ResultCode.AUTHORIZATION_DENIED);
                break modifyDNProcessing;
              }
              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);
                  }
                  op.setResultCode(ResultCode.valueOf(le.getResultCode()));
                  op.appendErrorMessage(le.getMessageObject());
                  break modifyDNProcessing;
                }
              }
              Entry authorizationEntry;
              try
              {
                authorizationEntry = proxyControl.getAuthorizationEntry();
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                op.setResultCode(de.getResultCode());
                op.appendErrorMessage(de.getMessageObject());
                break modifyDNProcessing;
              }
              op.setAuthorizationEntry(authorizationEntry);
              if (authorizationEntry == null)
              {
                op.setProxiedAuthorizationDN(DN.nullDN());
              }
              else
              {
                op.setProxiedAuthorizationDN(authorizationEntry.getDN());
              }
            }
            // NYI -- Add support for additional controls.
            else if (c.isCritical())
            {
              Backend backend = DirectoryServer.getBackend(entryDN);
              if ((backend == null) || (! backend.supportsControl(oid)))
              {
                op.setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
                op.appendErrorMessage(
                        ERR_MODDN_UNSUPPORTED_CRITICAL_CONTROL.get(
                                String.valueOf(entryDN),
                                oid));
                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.
        if (AccessControlConfigManager.getInstance()
            .getAccessControlHandler().isAllowed(op) == false) {
          op.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          op.appendErrorMessage(ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                  String.valueOf(entryDN)));
          skipPostOperation = true;
          break modifyDNProcessing;
        }
        // Duplicate the entry and set its new DN.  Also, create an empty list
        // to hold the attribute-level modifications.
        Entry newEntry = currentEntry.duplicate(false);
        newEntry.setDN(newDN);
        op.setUpdatedEntry(newEntry);
        // init the modifications
        op.addModification(null);
        List<Modification> modifications = op.getModifications();
        // If we should delete the old RDN values from the entry, then do so.
        if (op.deleteOldRDN())
        {
          RDN currentRDN = entryDN.getRDN();
          int numValues  = currentRDN.getNumValues();
          for (int i=0; i < numValues; i++)
          {
            LinkedHashSet<AttributeValue> valueSet =
                 new LinkedHashSet<AttributeValue>(1);
            valueSet.add(currentRDN.getAttributeValue(i));
            Attribute a = new Attribute(currentRDN.getAttributeType(i),
                                        currentRDN.getAttributeName(i),
                                        valueSet);
            // If the associated attribute type is marked NO-USER-MODIFICATION,
            // then refuse the update.
            if (a.getAttributeType().isNoUserModification())
            {
              if (! (op.isInternalOperation() ||
                  op.isSynchronizationOperation()))
              {
                op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                op.appendErrorMessage(ERR_MODDN_OLD_RDN_ATTR_IS_NO_USER_MOD.get(
                        String.valueOf(entryDN), a.getName()));
                break modifyDNProcessing;
              }
            }
            LinkedList<AttributeValue> missingValues =
                 new LinkedList<AttributeValue>();
            newEntry.removeAttribute(a, missingValues);
            if (missingValues.isEmpty())
            {
              modifications.add(new Modification(ModificationType.DELETE, a));
            }
          }
        }
        // Add the new RDN values to the entry.
        int newRDNValues = newRDN.getNumValues();
        for (int i=0; i < newRDNValues; i++)
        {
          LinkedHashSet<AttributeValue> valueSet =
               new LinkedHashSet<AttributeValue>(1);
          valueSet.add(newRDN.getAttributeValue(i));
          Attribute a = new Attribute(newRDN.getAttributeType(i),
                                      newRDN.getAttributeName(i),
                                      valueSet);
          LinkedList<AttributeValue> duplicateValues =
               new LinkedList<AttributeValue>();
          newEntry.addAttribute(a, duplicateValues);
          if (duplicateValues.isEmpty())
          {
            // If the associated attribute type is marked NO-USER-MODIFICATION,
            // then refuse the update.
            if (a.getAttributeType().isNoUserModification())
            {
              if (! (op.isInternalOperation() ||
                  op.isSynchronizationOperation()))
              {
                op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                op.appendErrorMessage(ERR_MODDN_NEW_RDN_ATTR_IS_NO_USER_MOD.get(
                        String.valueOf(entryDN), a.getName()));
                break modifyDNProcessing;
              }
            }
            else
            {
              modifications.add(new Modification(ModificationType.ADD, a));
            }
          }
        }
        // If the server is configured to check the schema and the
        // operation is not a synchronization operation,
        // make sure that the resulting entry is valid as per the server schema.
        if ((DirectoryServer.checkSchema()) &&
            (!op.isSynchronizationOperation()) )
        {
          MessageBuilder invalidReason = new MessageBuilder();
          if (! newEntry.conformsToSchema(null, false, true, true,
                                          invalidReason))
          {
            op.setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
            op.appendErrorMessage(ERR_MODDN_VIOLATES_SCHEMA.get(
                    String.valueOf(entryDN),
                    String.valueOf(invalidReason)));
            break modifyDNProcessing;
          }
          for (int i=0; i < newRDNValues; i++)
          {
            AttributeType at = newRDN.getAttributeType(i);
            if (at.isObsolete())
            {
              op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              op.appendErrorMessage(ERR_MODDN_NEWRDN_ATTR_IS_OBSOLETE.get(
                      String.valueOf(entryDN),
                      at.getNameOrOID()));
              break modifyDNProcessing;
            }
          }
        }
        // Check for a request to cancel this operation.
        if (op.getCancelRequest() != null)
        {
          return;
        }
        // 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 = op.getModifications().size();
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation modify DN plugins.
        if (!op.isSynchronizationOperation())
        {
          PreOperationPluginResult preOpResult =
            pluginConfigManager.invokePreOperationModifyDNPlugins(op);
          if (preOpResult.connectionTerminated())
          {
            // There's no point in continuing with anything.  Log the request
            // and result and return.
            op.setResultCode(ResultCode.CANCELED);
            op.appendErrorMessage(ERR_CANCELED_BY_PREOP_DISCONNECT.get());
            return;
          }
          else if (preOpResult.sendResponseImmediately())
          {
            skipPostOperation = true;
            break modifyDNProcessing;
          }
          else if (preOpResult.skipCoreProcessing())
          {
            skipPostOperation = false;
            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)
        {
          for (int i=modCount; i < modifications.size(); i++)
          {
            Modification m = modifications.get(i);
            Attribute    a = m.getAttribute();
            switch (m.getModificationType())
            {
              case ADD:
                LinkedList<AttributeValue> duplicateValues =
                     new LinkedList<AttributeValue>();
                newEntry.addAttribute(a, duplicateValues);
                break;
              case DELETE:
                LinkedList<AttributeValue> missingValues =
                     new LinkedList<AttributeValue>();
                newEntry.removeAttribute(a, missingValues);
                break;
              case REPLACE:
                duplicateValues = new LinkedList<AttributeValue>();
                newEntry.removeAttribute(a.getAttributeType(), a.getOptions());
                newEntry.addAttribute(a, duplicateValues);
                break;
              case INCREMENT:
                List<Attribute> attrList =
                     newEntry.getAttribute(a.getAttributeType(),
                                           a.getOptions());
                if ((attrList == null) || attrList.isEmpty())
                {
                  op.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
                  op.appendErrorMessage(ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get(
                          String.valueOf(entryDN), a.getName()));
                  break modifyDNProcessing;
                }
                else if (attrList.size() > 1)
                {
                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
                  op.appendErrorMessage(
                          ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get(
                                  String.valueOf(entryDN),
                                  a.getName()));
                  break modifyDNProcessing;
                }
                LinkedHashSet<AttributeValue> values =
                     attrList.get(0).getValues();
                if ((values == null) || values.isEmpty())
                {
                  op.setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
                  op.appendErrorMessage(ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get(
                          String.valueOf(entryDN),
                          a.getName()));
                  break modifyDNProcessing;
                }
                else if (values.size() > 1)
                {
                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
                  op.appendErrorMessage(
                          ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get(
                                  String.valueOf(entryDN),
                                  a.getName()));
                  break modifyDNProcessing;
                }
                long currentLongValue;
                try
                {
                  AttributeValue v = values.iterator().next();
                  currentLongValue = Long.parseLong(v.getStringValue());
                }
                catch (Exception e)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                  }
                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
                  op.appendErrorMessage(
                          ERR_MODDN_PREOP_INCREMENT_VALUE_NOT_INTEGER.get(
                                  String.valueOf(entryDN),
                                  a.getName()));
                  break modifyDNProcessing;
                }
                LinkedHashSet<AttributeValue> newValues = a.getValues();
                if ((newValues == null) || newValues.isEmpty())
                {
                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
                  op.appendErrorMessage(
                          ERR_MODDN_PREOP_INCREMENT_NO_AMOUNT.get(
                                  String.valueOf(entryDN),
                                  a.getName()));
                  break modifyDNProcessing;
                }
                else if (newValues.size() > 1)
                {
                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
                  op.appendErrorMessage(
                          ERR_MODDN_PREOP_INCREMENT_MULTIPLE_AMOUNTS.get(
                                  String.valueOf(entryDN),
                                  a.getName()));
                  break modifyDNProcessing;
                }
                long incrementAmount;
                try
                {
                  AttributeValue v = values.iterator().next();
                  incrementAmount = Long.parseLong(v.getStringValue());
                }
                catch (Exception e)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                  }
                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
                  op.appendErrorMessage(
                          ERR_MODDN_PREOP_INCREMENT_AMOUNT_NOT_INTEGER.get(
                                  String.valueOf(entryDN),
                                  a.getName()));
                  break modifyDNProcessing;
                }
                long newLongValue = currentLongValue + incrementAmount;
                ByteString newValueOS =
                     new ASN1OctetString(String.valueOf(newLongValue));
                newValues = new LinkedHashSet<AttributeValue>(1);
                newValues.add(new AttributeValue(a.getAttributeType(),
                                                 newValueOS));
                List<Attribute> newAttrList = new ArrayList<Attribute>(1);
                newAttrList.add(new Attribute(a.getAttributeType(),
                                              a.getName(), newValues));
                newEntry.putAttribute(a.getAttributeType(), newAttrList);
                break;
            }
          }
          // Make sure that the updated entry still conforms to the server
          // schema.
          if (DirectoryServer.checkSchema())
          {
            MessageBuilder invalidReason = new MessageBuilder();
            if (! newEntry.conformsToSchema(null, false, true, true,
                                            invalidReason))
            {
              op.setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
              op.appendErrorMessage(ERR_MODDN_PREOP_VIOLATES_SCHEMA.get(
                      String.valueOf(entryDN),
                      String.valueOf(invalidReason)));
              break modifyDNProcessing;
            }
          }
        }
        // Check for a request to cancel this operation.
        if (op.getCancelRequest() != null)
        {
          return;
        }
        // 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:
                op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                op.appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
                        String.valueOf(entryDN)));
                break modifyDNProcessing;
              case INTERNAL_ONLY:
                if (! (op.isInternalOperation() ||
                    op.isSynchronizationOperation()))
                {
                  op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  op.appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
                          String.valueOf(entryDN)));
                  break modifyDNProcessing;
                }
            }
            switch (currentBackend.getWritabilityMode())
            {
              case DISABLED:
                op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                op.appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
                        String.valueOf(entryDN)));
                break modifyDNProcessing;
              case INTERNAL_ONLY:
                if (! (op.isInternalOperation() ||
                    op.isSynchronizationOperation()))
                {
                  op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  op.appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
                          String.valueOf(entryDN)));
                  break modifyDNProcessing;
                }
            }
          }
          if (noOp)
          {
            op.appendErrorMessage(INFO_MODDN_NOOP.get());
            op.setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
            for (SynchronizationProvider provider :
                 DirectoryServer.getSynchronizationProviders())
            {
              try
              {
                SynchronizationProviderResult result =
                     provider.doPreOperation(op);
                if (! result.continueOperationProcessing())
                {
                  break modifyDNProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                logError(ERR_MODDN_SYNCH_PREOP_FAILED.
                    get(op.getConnectionID(), op.getOperationID(),
                        getExceptionMessage(de)));
                op.setResponseData(de);
                break modifyDNProcessing;
              }
            }
            currentBackend.renameEntry(entryDN, newEntry, op);
          }
          if (preReadRequest != null)
          {
            Entry entry = currentEntry.duplicate(true);
            if (! preReadRequest.allowsAttribute(
                       DirectoryServer.getObjectClassAttributeType()))
            {
              entry.removeAttribute(
                   DirectoryServer.getObjectClassAttributeType());
            }
            if (! preReadRequest.returnAllUserAttributes())
            {
              Iterator<AttributeType> iterator =
                   entry.getUserAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! preReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            if (! preReadRequest.returnAllOperationalAttributes())
            {
              Iterator<AttributeType> iterator =
                   entry.getOperationalAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! preReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            // FIXME -- Check access controls on the entry to see if it should
            //          be returned or if any attributes need to be stripped
            //          out..
            SearchResultEntry searchEntry = new SearchResultEntry(entry);
            LDAPPreReadResponseControl responseControl =
                 new LDAPPreReadResponseControl(preReadRequest.getOID(),
                                                preReadRequest.isCritical(),
                                                searchEntry);
            op.addResponseControl(responseControl);
          }
          if (postReadRequest != null)
          {
            Entry entry = newEntry.duplicate(true);
            if (! postReadRequest.allowsAttribute(
                       DirectoryServer.getObjectClassAttributeType()))
            {
              entry.removeAttribute(
                   DirectoryServer.getObjectClassAttributeType());
            }
            if (! postReadRequest.returnAllUserAttributes())
            {
              Iterator<AttributeType> iterator =
                   entry.getUserAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! postReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            if (! postReadRequest.returnAllOperationalAttributes())
            {
              Iterator<AttributeType> iterator =
                   entry.getOperationalAttributes().keySet().iterator();
              while (iterator.hasNext())
              {
                AttributeType attrType = iterator.next();
                if (! postReadRequest.allowsAttribute(attrType))
                {
                  iterator.remove();
                }
              }
            }
            // FIXME -- Check access controls on the entry to see if it should
            //          be returned or if any attributes need to be stripped
            //          out..
            SearchResultEntry searchEntry = new SearchResultEntry(entry);
            LDAPPostReadResponseControl responseControl =
                 new LDAPPostReadResponseControl(postReadRequest.getOID(),
                                                 postReadRequest.isCritical(),
                                                 searchEntry);
            op.addResponseControl(responseControl);
          }
          if (! noOp)
          {
            op.setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          op.setResultCode(de.getResultCode());
          op.appendErrorMessage(de.getMessageObject());
          op.setMatchedDN(de.getMatchedDN());
          op.setReferralURLs(de.getReferralURLs());
          break modifyDNProcessing;
        }
        catch (CancelledOperationException coe)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
          }
          CancelResult cancelResult = coe.getCancelResult();
          op.setCancelResult(cancelResult);
          op.setResultCode(cancelResult.getResultCode());
          Message message = coe.getMessageObject();
          if ((message != null) && (message.length() > 0))
          {
            op.appendErrorMessage(message);
          }
          break modifyDNProcessing;
        }
      }
      finally
      {
        LockManager.unlock(entryDN, currentLock);
        LockManager.unlock(newDN, newLock);
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.doPostOperation(op);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_MODDN_SYNCH_POSTOP_FAILED.
                get(op.getConnectionID(), op.getOperationID(),
                    getExceptionMessage(de)));
            op.setResponseData(de);
            break;
          }
        }
      }
    }
    // Indicate that it is now too late to attempt to cancel the operation.
    op.setCancelResult(CancelResult.TOO_LATE);
    // Invoke the post-operation or post-synchronization modify DN plugins.
    if (op.isSynchronizationOperation())
    {
      if (op.getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationModifyDNPlugins(op);
      }
    }
    else if (! skipPostOperation)
    {
      PostOperationPluginResult postOperationResult =
           pluginConfigManager.invokePostOperationModifyDNPlugins(op);
      if (postOperationResult.connectionTerminated())
      {
        op.setResultCode(ResultCode.CANCELED);
        op.appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
        return;
      }
    }
    // Notify any change notification listeners that might be registered with
    // the server.
    if (op.getResultCode() == ResultCode.SUCCESS)
    {
      for (ChangeNotificationListener changeListener :
           DirectoryServer.getChangeNotificationListeners())
      {
        try
        {
          changeListener.handleModifyDNOperation(op,
              op.getOriginalEntry(),
              op.getUpdatedEntry());
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER.get(
              getExceptionMessage(e));
          logError(message);
        }
      }
    }
  }
  /**
   * Attaches the current local operation to the global operation so that
@@ -8737,11 +269,8 @@
   *                               operation
   */
  @SuppressWarnings("unchecked")
  public static final <O extends Operation, L>
  void attachLocalOperation (
      O globalOperation,
      L currentLocalOperation
      )
  public static final <O extends Operation,L> void
              attachLocalOperation (O globalOperation, L currentLocalOperation)
  {
    List<?> existingAttachment =
      (List<?>) globalOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
@@ -8756,8 +285,8 @@
      newAttachment.addAll ((List<L>) existingAttachment);
    }
    newAttachment.add (currentLocalOperation);
    globalOperation.setAttachment(
        Operation.LOCALBACKENDOPERATIONS, newAttachment);
    globalOperation.setAttachment(Operation.LOCALBACKENDOPERATIONS,
                                  newAttachment);
  }
}