From b1e3b0ccdaa423b68ef6fa2fee67d3e09990985f Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Mon, 24 Sep 2007 23:15:46 +0000
Subject: [PATCH] Perform a significant refactoring of the local backend workflow element classes so that they use smaller method sizes, which allows the VM to better optimize the code.  Also, move the code for processing each type of operation into the class for the associated operation rather than keeping it all in the LocalBackendWorkflowElement class.

---
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java | 1347 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1,328 insertions(+), 19 deletions(-)

diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
index b1e1b32..8e4ec8d 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -26,14 +26,71 @@
  */
 package org.opends.server.workflowelement.localbackend;
 
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.server.api.Backend;
+import org.opends.server.api.ChangeNotificationListener;
+import org.opends.server.api.ClientConnection;
+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.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.ProxiedAuthV1Control;
+import org.opends.server.controls.ProxiedAuthV2Control;
+import org.opends.server.core.AccessControlConfigManager;
+import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.core.ModifyDNOperationWrapper;
+import org.opends.server.core.PluginConfigManager;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelledOperationException;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
+import org.opends.server.types.LDAPException;
+import org.opends.server.types.LockManager;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+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.operation.PostOperationModifyDNOperation;
 import org.opends.server.types.operation.PostResponseModifyDNOperation;
 import org.opends.server.types.operation.PreOperationModifyDNOperation;
 import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
 
+import static org.opends.messages.CoreMessages.*;
+import static org.opends.server.loggers.ErrorLogger.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+
+
 /**
  * This class defines an operation used to move an entry in a local backend
  * of the Directory Server.
@@ -45,12 +102,45 @@
              PostResponseModifyDNOperation,
              PostSynchronizationModifyDNOperation
 {
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+
+
+  // The backend in which the operation is to be processed.
+  private Backend backend;
+
+  // Indicates whether the no-op control was included in the request.
+  private boolean noOp;
+
+  // Indicates whether to skip post-operation plugin processing.
+  private boolean skipPostOperation;
+
+  // The client connection on which this operation was requested.
+  private ClientConnection clientConnection;
+
+  // The original DN of the entry.
+  DN entryDN;
+
   // The current entry, before it is renamed.
   private Entry currentEntry;
 
   // The new entry, as it will appear after it has been renamed.
   private Entry newEntry;
 
+  // The LDAP post-read request control, if present in the request.
+  private LDAPPostReadRequestControl postReadRequest;
+
+  // The LDAP pre-read request control, if present in the request.
+  private LDAPPreReadRequestControl preReadRequest;
+
+  // The new RDN for the entry.
+  private RDN newRDN;
+
+
+
   /**
    * Creates a new operation that may be used to move an entry in a
    * local backend of the Directory Server.
@@ -63,6 +153,8 @@
     LocalBackendWorkflowElement.attachLocalOperation (operation, this);
   }
 
+
+
   /**
    * Retrieves the current entry, before it is renamed.  This will not be
    * available to pre-parse plugins or during the conflict resolution portion of
@@ -76,6 +168,8 @@
     return currentEntry;
   }
 
+
+
   /**
    * Retrieves the new entry, as it will appear after it is renamed.  This will
    * not be  available to pre-parse plugins or during the conflict resolution
@@ -89,31 +183,1246 @@
     return newEntry;
   }
 
-  /**
-   * Sets the current entry, before it is renamed.  This will not be
-   * available to pre-parse plugins or during the conflict resolution portion of
-   * the synchronization processing.
-   *
-   * @param entry  The current entry, or <CODE>null</CODE> if it is not yet
-   *           available.
-   */
-  public final void setOriginalEntry(Entry entry)
-  {
-    this.currentEntry = entry;
-  }
+
 
   /**
-   * Sets the new entry, as it will appear after it is renamed.  This will
-   * not be  available to pre-parse plugins or during the conflict resolution
-   * portion of the synchronization processing.
+   * Process this modify DN operation in a local backend.
    *
-   * @param entry  The updated entry, or <CODE>null</CODE> if it is not yet
-   *           available.
+   * @param  backend  The backend in which the modify DN operation should be
+   *                  processed.
    */
-  public final void setUpdatedEntry(Entry entry)
+  void processLocalModifyDN(Backend backend)
   {
-    this.newEntry = entry;
+    this.backend = backend;
+
+    clientConnection = getClientConnection();
+
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+         DirectoryServer.getPluginConfigManager();
+    skipPostOperation = false;
+
+    // Check for a request to cancel this operation.
+    if (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.
+      entryDN = getEntryDN();
+
+      newRDN = getNewRDN();
+      if (newRDN == null)
+      {
+        skipPostOperation = true;
+        break modifyDNProcessing;
+      }
+
+      DN newSuperior = getNewSuperior();
+      if ((newSuperior == null) &&
+          (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())
+      {
+        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+        appendErrorMessage(ERR_MODDN_NO_PARENT.get(String.valueOf(entryDN)));
+        break modifyDNProcessing;
+      }
+
+      DN newDN = parentDN.concat(newRDN);
+
+      // Get the backend for the current entry, and the backend for the new
+      // entry.  If either is null, or if they are different, then fail.
+      Backend currentBackend = backend;
+      if (currentBackend == null)
+      {
+        setResultCode(ResultCode.NO_SUCH_OBJECT);
+        appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get(
+                                String.valueOf(entryDN)));
+        break modifyDNProcessing;
+      }
+
+      Backend newBackend = DirectoryServer.getBackend(newDN);
+      if (newBackend == null)
+      {
+        setResultCode(ResultCode.NO_SUCH_OBJECT);
+        appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get(
+                                String.valueOf(entryDN),
+                                String.valueOf(newDN)));
+        break modifyDNProcessing;
+      }
+      else if (! currentBackend.equals(newBackend))
+      {
+        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+        appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get(
+                                String.valueOf(entryDN),
+                                String.valueOf(newDN)));
+        break modifyDNProcessing;
+      }
+
+
+      // Check for a request to cancel this operation.
+      if (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)
+      {
+        setResultCode(DirectoryServer.getServerErrorResultCode());
+        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);
+        }
+
+        setResultCode(DirectoryServer.getServerErrorResultCode());
+        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);
+
+        setResultCode(DirectoryServer.getServerErrorResultCode());
+        appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(
+                                String.valueOf(entryDN),
+                                String.valueOf(newDN)));
+        skipPostOperation = true;
+        break modifyDNProcessing;
+      }
+
+      try
+      {
+        // Check for a request to cancel this operation.
+        if (getCancelRequest() != null)
+        {
+          return;
+        }
+
+
+        // Get the current entry from the appropriate backend.  If it doesn't
+        // exist, then fail.
+        try
+        {
+          currentEntry = currentBackend.getEntry(entryDN);
+        }
+        catch (DirectoryException de)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, de);
+          }
+
+          setResponseData(de);
+          break modifyDNProcessing;
+        }
+
+        if (getOriginalEntry() == null)
+        {
+          // See if one of the entry's ancestors exists.
+          parentDN = entryDN.getParentDNInSuffix();
+          while (parentDN != null)
+          {
+            try
+            {
+              if (DirectoryServer.entryExists(parentDN))
+              {
+                setMatchedDN(parentDN);
+                break;
+              }
+            }
+            catch (Exception e)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, e);
+              }
+              break;
+            }
+
+            parentDN = parentDN.getParentDNInSuffix();
+          }
+
+          setResultCode(ResultCode.NO_SUCH_OBJECT);
+          appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get(
+                                  String.valueOf(entryDN)));
+          break modifyDNProcessing;
+        }
+
+
+        // Invoke any conflict resolution processing that might be needed by the
+        // synchronization provider.
+        for (SynchronizationProvider provider :
+             DirectoryServer.getSynchronizationProviders())
+        {
+          try
+          {
+            SynchronizationProviderResult result =
+                 provider.handleConflictResolution(this);
+            if (! result.continueOperationProcessing())
+            {
+              break modifyDNProcessing;
+            }
+          }
+          catch (DirectoryException de)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            }
+
+            logError(ERR_MODDN_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
+                          getConnectionID(), getOperationID(),
+                          getExceptionMessage(de)));
+
+            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.
+        try
+        {
+          handleRequestControls();
+        }
+        catch (DirectoryException de)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, de);
+          }
+
+          setResponseData(de);
+          break modifyDNProcessing;
+        }
+
+
+        // Check to see if the client has permission to perform the
+        // modify DN.
+
+        // FIXME: for now assume that this will check all permission
+        // pertinent to the operation. This includes proxy authorization
+        // and any other controls specified.
+
+        // FIXME: earlier checks to see if the entry or new superior
+        // already exists may have already exposed sensitive information
+        // to the client.
+        if (! AccessControlConfigManager.getInstance().
+                   getAccessControlHandler().isAllowed(this))
+        {
+          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+          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.
+        newEntry = currentEntry.duplicate(false);
+        newEntry.setDN(newDN);
+
+        // init the modifications
+        addModification(null);
+        List<Modification> modifications = this.getModifications();
+
+
+
+        // Apply any changes to the entry based on the change in its RDN.  Also,
+        // perform schema checking on the updated entry.
+        try
+        {
+          applyRDNChanges(modifications);
+        }
+        catch (DirectoryException de)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, de);
+          }
+
+          setResponseData(de);
+          break modifyDNProcessing;
+        }
+
+
+        // Check for a request to cancel this operation.
+        if (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 = modifications.size();
+
+
+        // If the operation is not a synchronization operation,
+        // Invoke the pre-operation modify DN plugins.
+        if (! isSynchronizationOperation())
+        {
+          PreOperationPluginResult preOpResult =
+            pluginConfigManager.invokePreOperationModifyDNPlugins(this);
+          if (preOpResult.connectionTerminated())
+          {
+            // There's no point in continuing with anything.  Log the request
+            // and result and return.
+            setResultCode(ResultCode.CANCELED);
+            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)
+        {
+          try
+          {
+            applyPreOpModifications(modifications, modCount);
+          }
+          catch (DirectoryException de)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            }
+
+            setResponseData(de);
+            break modifyDNProcessing;
+          }
+        }
+
+
+        // Check for a request to cancel this operation.
+        if (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:
+                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+                appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
+                                        String.valueOf(entryDN)));
+                break modifyDNProcessing;
+
+              case INTERNAL_ONLY:
+                if (! (isInternalOperation() || isSynchronizationOperation()))
+                {
+                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+                  appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
+                                          String.valueOf(entryDN)));
+                  break modifyDNProcessing;
+                }
+            }
+
+            switch (currentBackend.getWritabilityMode())
+            {
+              case DISABLED:
+                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+                appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
+                                        String.valueOf(entryDN)));
+                break modifyDNProcessing;
+
+              case INTERNAL_ONLY:
+                if (! (isInternalOperation() || isSynchronizationOperation()))
+                {
+                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+                  appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
+                                          String.valueOf(entryDN)));
+                  break modifyDNProcessing;
+                }
+            }
+          }
+
+
+          if (noOp)
+          {
+            appendErrorMessage(INFO_MODDN_NOOP.get());
+            setResultCode(ResultCode.NO_OPERATION);
+          }
+          else
+          {
+            for (SynchronizationProvider provider :
+                 DirectoryServer.getSynchronizationProviders())
+            {
+              try
+              {
+                SynchronizationProviderResult result =
+                     provider.doPreOperation(this);
+                if (! result.continueOperationProcessing())
+                {
+                  break modifyDNProcessing;
+                }
+              }
+              catch (DirectoryException de)
+              {
+                if (debugEnabled())
+                {
+                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
+                }
+
+                logError(ERR_MODDN_SYNCH_PREOP_FAILED.get(getConnectionID(),
+                              getOperationID(), getExceptionMessage(de)));
+                setResponseData(de);
+                break modifyDNProcessing;
+              }
+            }
+
+            currentBackend.renameEntry(entryDN, newEntry, this);
+          }
+
+
+          // Attach the pre-read and/or post-read controls to the response if
+          // appropriate.
+          processReadEntryControls();
+
+
+          if (! noOp)
+          {
+            setResultCode(ResultCode.SUCCESS);
+          }
+        }
+        catch (DirectoryException de)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, de);
+          }
+
+          setResponseData(de);
+          break modifyDNProcessing;
+        }
+        catch (CancelledOperationException coe)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
+          }
+
+          CancelResult cancelResult = coe.getCancelResult();
+
+          setCancelResult(cancelResult);
+          setResultCode(cancelResult.getResultCode());
+
+          Message message = coe.getMessageObject();
+          if ((message != null) && (message.length() > 0))
+          {
+            appendErrorMessage(message);
+          }
+
+          break modifyDNProcessing;
+        }
+      }
+      finally
+      {
+        LockManager.unlock(entryDN, currentLock);
+        LockManager.unlock(newDN, newLock);
+
+        for (SynchronizationProvider provider :
+             DirectoryServer.getSynchronizationProviders())
+        {
+          try
+          {
+            provider.doPostOperation(this);
+          }
+          catch (DirectoryException de)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            }
+
+            logError(ERR_MODDN_SYNCH_POSTOP_FAILED.get(getConnectionID(),
+                          getOperationID(), getExceptionMessage(de)));
+            setResponseData(de);
+            break;
+          }
+        }
+      }
+    }
+
+
+    // Indicate that it is now too late to attempt to cancel the operation.
+    setCancelResult(CancelResult.TOO_LATE);
+
+
+    // Invoke the post-operation or post-synchronization modify DN plugins.
+    if (isSynchronizationOperation())
+    {
+      if (getResultCode() == ResultCode.SUCCESS)
+      {
+        pluginConfigManager.invokePostSynchronizationModifyDNPlugins(this);
+      }
+    }
+    else if (! skipPostOperation)
+    {
+      PostOperationPluginResult postOperationResult =
+           pluginConfigManager.invokePostOperationModifyDNPlugins(this);
+      if (postOperationResult.connectionTerminated())
+      {
+        setResultCode(ResultCode.CANCELED);
+        appendErrorMessage(ERR_CANCELED_BY_POSTOP_DISCONNECT.get());
+        return;
+      }
+    }
+
+
+    // Notify any change notification listeners that might be registered with
+    // the server.
+    if (getResultCode() == ResultCode.SUCCESS)
+    {
+      for (ChangeNotificationListener changeListener :
+           DirectoryServer.getChangeNotificationListeners())
+      {
+        try
+        {
+          changeListener.handleModifyDNOperation(this, currentEntry, newEntry);
+        }
+        catch (Exception e)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+
+          Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER.get(
+              getExceptionMessage(e));
+          logError(message);
+        }
+      }
+    }
   }
 
 
+
+  /**
+   * Processes the set of controls included in the request.
+   *
+   * @throws  DirectoryException  If a problem occurs that should cause the
+   *                              modify DN operation to fail.
+   */
+  private void handleRequestControls()
+          throws DirectoryException
+  {
+    List<Control> requestControls = getRequestControls();
+    if ((requestControls != null) && (! requestControls.isEmpty()))
+    {
+      for (int i=0; i < requestControls.size(); i++)
+      {
+        Control c   = requestControls.get(i);
+        String  oid = c.getOID();
+
+        if (! AccessControlConfigManager.getInstance().
+                   getAccessControlHandler().isAllowed(entryDN,  this, c))
+        {
+          skipPostOperation = true;
+          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
+        }
+
+        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);
+              }
+
+              throw new DirectoryException(
+                             ResultCode.valueOf(le.getResultCode()),
+                             le.getMessageObject());
+            }
+          }
+
+          try
+          {
+            // FIXME -- We need to determine whether the current user has
+            //          permission to make this determination.
+            SearchFilter filter = assertControl.getSearchFilter();
+            if (! filter.matchesEntry(currentEntry))
+            {
+              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
+                                           ERR_MODDN_ASSERTION_FAILED.get(
+                                                String.valueOf(entryDN)));
+            }
+          }
+          catch (DirectoryException de)
+          {
+            if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
+            {
+              throw de;
+            }
+
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, de);
+            }
+
+            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
+                           ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(
+                                String.valueOf(entryDN),
+                                de.getMessageObject()));
+          }
+        }
+        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);
+              }
+
+              throw new DirectoryException(
+                             ResultCode.valueOf(le.getResultCode()),
+                             le.getMessageObject());
+            }
+          }
+        }
+        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);
+              }
+
+              throw new DirectoryException(
+                             ResultCode.valueOf(le.getResultCode()),
+                             le.getMessageObject());
+            }
+          }
+        }
+        else if (oid.equals(OID_PROXIED_AUTH_V1))
+        {
+          // The requester must have the PROXIED_AUTH privilige in order to
+          // be able to use this control.
+          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+          {
+            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+          }
+
+
+          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);
+              }
+
+              throw new DirectoryException(
+                             ResultCode.valueOf(le.getResultCode()),
+                             le.getMessageObject());
+            }
+          }
+
+
+          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
+          setAuthorizationEntry(authorizationEntry);
+          if (authorizationEntry == null)
+          {
+            setProxiedAuthorizationDN(DN.nullDN());
+          }
+          else
+          {
+            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, this))
+          {
+            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+          }
+
+
+          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);
+              }
+
+              throw new DirectoryException(
+                             ResultCode.valueOf(le.getResultCode()),
+                             le.getMessageObject());
+            }
+          }
+
+
+          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
+          setAuthorizationEntry(authorizationEntry);
+          if (authorizationEntry == null)
+          {
+            setProxiedAuthorizationDN(DN.nullDN());
+          }
+          else
+          {
+            setProxiedAuthorizationDN(authorizationEntry.getDN());
+          }
+        }
+
+        // NYI -- Add support for additional controls.
+
+        else if (c.isCritical())
+        {
+          if ((backend == null) || (! backend.supportsControl(oid)))
+          {
+            throw new DirectoryException(
+                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
+                           ERR_MODDN_UNSUPPORTED_CRITICAL_CONTROL.get(
+                                String.valueOf(entryDN), oid));
+          }
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Updates the entry so that its attributes are changed to reflect the changes
+   * to the RDN.  This also performs schema checking on the updated entry.
+   *
+   * @param  modifications  A list to hold the modifications made to the entry.
+   *
+   * @throws  DirectoryException  If a problem occurs that should cause the
+   *                              modify DN operation to fail.
+   */
+  private void applyRDNChanges(List<Modification> modifications)
+          throws DirectoryException
+  {
+    // If we should delete the old RDN values from the entry, then do so.
+    if (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 (! (isInternalOperation() || isSynchronizationOperation()))
+          {
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                           ERR_MODDN_OLD_RDN_ATTR_IS_NO_USER_MOD.get(
+                                String.valueOf(entryDN), a.getName()));
+          }
+        }
+
+        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 (! (isInternalOperation() || isSynchronizationOperation()))
+          {
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                           ERR_MODDN_NEW_RDN_ATTR_IS_NO_USER_MOD.get(
+                                String.valueOf(entryDN), a.getName()));
+          }
+        }
+        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()) && (! isSynchronizationOperation()))
+    {
+      MessageBuilder invalidReason = new MessageBuilder();
+      if (! newEntry.conformsToSchema(null, false, true, true,
+                                      invalidReason))
+      {
+        throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
+                                     ERR_MODDN_VIOLATES_SCHEMA.get(
+                                          String.valueOf(entryDN),
+                                          String.valueOf(invalidReason)));
+      }
+
+      for (int i=0; i < newRDNValues; i++)
+      {
+        AttributeType at = newRDN.getAttributeType(i);
+        if (at.isObsolete())
+        {
+          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                       ERR_MODDN_NEWRDN_ATTR_IS_OBSOLETE.get(
+                                            String.valueOf(entryDN),
+                                            at.getNameOrOID()));
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Applies any modifications performed during pre-operation plugin processing.
+   * This also performs schema checking for the updated entry.
+   *
+   * @param  modifications  A list containing the modifications made to the
+   *                        entry.
+   * @param  startPos       The position in the list at which the pre-operation
+   *                        modifications start.
+   *
+   * @throws  DirectoryException  If a problem occurs that should cause the
+   *                              modify DN operation to fail.
+   */
+  private void applyPreOpModifications(List<Modification> modifications,
+                                       int startPos)
+          throws DirectoryException
+  {
+    for (int i=startPos; 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())
+          {
+            throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
+                                         ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get(
+                                              String.valueOf(entryDN),
+                                              a.getName()));
+          }
+          else if (attrList.size() > 1)
+          {
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                           ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get(
+                                String.valueOf(entryDN), a.getName()));
+          }
+
+          LinkedHashSet<AttributeValue> values =
+               attrList.get(0).getValues();
+          if ((values == null) || values.isEmpty())
+          {
+            throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
+                                         ERR_MODDN_PREOP_INCREMENT_NO_ATTR.get(
+                                              String.valueOf(entryDN),
+                                              a.getName()));
+          }
+          else if (values.size() > 1)
+          {
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                           ERR_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES.get(
+                                String.valueOf(entryDN), a.getName()));
+          }
+
+          long currentLongValue;
+          try
+          {
+            AttributeValue v = values.iterator().next();
+            currentLongValue = Long.parseLong(v.getStringValue());
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                           ERR_MODDN_PREOP_INCREMENT_VALUE_NOT_INTEGER.get(
+                                String.valueOf(entryDN), a.getName()));
+          }
+
+          LinkedHashSet<AttributeValue> newValues = a.getValues();
+          if ((newValues == null) || newValues.isEmpty())
+          {
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                           ERR_MODDN_PREOP_INCREMENT_NO_AMOUNT.get(
+                                String.valueOf(entryDN), a.getName()));
+          }
+          else if (newValues.size() > 1)
+          {
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                           ERR_MODDN_PREOP_INCREMENT_MULTIPLE_AMOUNTS.get(
+                                String.valueOf(entryDN), a.getName()));
+          }
+
+          long incrementAmount;
+          try
+          {
+            AttributeValue v = values.iterator().next();
+            incrementAmount = Long.parseLong(v.getStringValue());
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                           ERR_MODDN_PREOP_INCREMENT_AMOUNT_NOT_INTEGER.get(
+                                String.valueOf(entryDN), a.getName()));
+          }
+
+          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))
+      {
+        throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
+                                     ERR_MODDN_PREOP_VIOLATES_SCHEMA.get(
+                                          String.valueOf(entryDN),
+                                          String.valueOf(invalidReason)));
+      }
+    }
+  }
+
+
+
+  /**
+   * Performs any necessary processing to create the pre-read and/or post-read
+   * response controls and attach them to the response.
+   */
+  private void processReadEntryControls()
+  {
+    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);
+
+      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);
+
+      addResponseControl(responseControl);
+    }
+  }
 }
+

--
Gitblit v1.10.0