From ff9755c40feeaf5a3208229d48b80b608ab7a68c Mon Sep 17 00:00:00 2001
From: jarnou <jarnou@localhost>
Date: Tue, 17 Jul 2007 08:14:43 +0000
Subject: [PATCH] This fix is the refactoring of the modrdn operation [issue 1180]

---
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java | 1300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1,300 insertions(+), 0 deletions(-)

diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
index 7d85435..2012bee 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -77,6 +77,7 @@
 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;
@@ -179,6 +180,9 @@
     case MODIFY:
       processModify((ModifyOperation) operation);
       break;
+    case MODIFY_DN:
+      processModifyDN((ModifyDNOperation) operation);
+      break;
     case COMPARE:
       processCompare((CompareOperation) operation);
       break;
@@ -6679,6 +6683,7 @@
   }
 
 
+
   /**
    * Perform a compare operation against a local backend.
    *
@@ -7248,6 +7253,1301 @@
     }
   }
 
+  /**
+   * 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 operation - 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 and handle a request to cancel this operation.
+    if (op.getCancelRequest() != null)
+    {
+      op.indicateCancelled(op.getCancelRequest());
+      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(getMessage(MSGID_MODDN_NO_PARENT,
+                                      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(getMessage(
+                                      MSGID_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY,
+                                      String.valueOf(entryDN)));
+        break modifyDNProcessing;
+      }
+
+      Backend newBackend = DirectoryServer.getBackend(newDN);
+      if (newBackend == null)
+      {
+        op.setResultCode(ResultCode.NO_SUCH_OBJECT);
+        op.appendErrorMessage(getMessage(MSGID_MODDN_NO_BACKEND_FOR_NEW_ENTRY,
+                                      String.valueOf(entryDN),
+                                      String.valueOf(newDN)));
+        break modifyDNProcessing;
+      }
+      else if (! currentBackend.equals(newBackend))
+      {
+        op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+        op.appendErrorMessage(getMessage(MSGID_MODDN_DIFFERENT_BACKENDS,
+                                      String.valueOf(entryDN),
+                                      String.valueOf(newDN)));
+        break modifyDNProcessing;
+      }
+
+
+      // Check for and handle a request to cancel this operation.
+      if (op.getCancelRequest() != null)
+      {
+        op.indicateCancelled(op.getCancelRequest());
+        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(getMessage(MSGID_MODDN_CANNOT_LOCK_CURRENT_DN,
+                                      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(getMessage(MSGID_MODDN_EXCEPTION_LOCKING_NEW_DN,
+                                      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(getMessage(MSGID_MODDN_CANNOT_LOCK_NEW_DN,
+                                      String.valueOf(entryDN),
+                                      String.valueOf(newDN)));
+
+        skipPostOperation = true;
+        break modifyDNProcessing;
+      }
+
+      Entry currentEntry = null;
+      try
+      {
+        // Check for and handle a request to cancel this operation.
+        if (op.getCancelRequest() != null)
+        {
+          op.indicateCancelled(op.getCancelRequest());
+          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.getErrorMessage());
+          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(getMessage(MSGID_MODDN_NO_CURRENT_ENTRY,
+                                        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(ErrorLogCategory.SYNCHRONIZATION,
+                     ErrorLogSeverity.SEVERE_ERROR,
+                     MSGID_MODDN_SYNCH_CONFLICT_RESOLUTION_FAILED,
+                     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 (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.getMessage());
+
+                  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(getMessage(MSGID_MODDN_ASSERTION_FAILED,
+                                                String.valueOf(entryDN)));
+
+                  break modifyDNProcessing;
+                }
+              }
+              catch (DirectoryException de)
+              {
+                if (debugEnabled())
+                {
+                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
+                }
+
+                op.setResultCode(ResultCode.PROTOCOL_ERROR);
+
+                int msgID = MSGID_MODDN_CANNOT_PROCESS_ASSERTION_FILTER;
+                op.appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
+                                              de.getErrorMessage()));
+
+                break modifyDNProcessing;
+              }
+            }
+            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
+            {
+              noOp = true;
+            }
+            else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
+            {
+              if (c instanceof LDAPAssertionRequestControl)
+              {
+                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.getMessage());
+
+                  break modifyDNProcessing;
+                }
+              }
+            }
+            else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
+            {
+              if (c instanceof LDAPAssertionRequestControl)
+              {
+                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.getMessage());
+
+                  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))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                op.appendErrorMessage(getMessage(msgID));
+                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.getMessage());
+
+                  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.getErrorMessage());
+
+                break modifyDNProcessing;
+              }
+
+              if (AccessControlConfigManager.getInstance()
+                      .getAccessControlHandler().isProxiedAuthAllowed(op,
+                      authorizationEntry) == false) {
+                op.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+                int msgID = MSGID_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+                op.appendErrorMessage(getMessage(msgID,
+                    String.valueOf(entryDN)));
+
+                skipPostOperation = true;
+                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))
+              {
+                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
+                op.appendErrorMessage(getMessage(msgID));
+                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.getMessage());
+
+                  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.getErrorMessage());
+
+                break modifyDNProcessing;
+              }
+              if (AccessControlConfigManager.getInstance()
+                  .getAccessControlHandler().isProxiedAuthAllowed(op,
+                                                authorizationEntry) == false) {
+                op.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+                int msgID = MSGID_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+                op.appendErrorMessage(getMessage(msgID,
+                    String.valueOf(entryDN)));
+
+                skipPostOperation = true;
+                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);
+
+                int msgID = MSGID_MODDN_UNSUPPORTED_CRITICAL_CONTROL;
+                op.appendErrorMessage(getMessage(msgID, 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);
+
+          int msgID = MSGID_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
+          op.appendErrorMessage(getMessage(msgID, 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);
+
+                int msgID = MSGID_MODDN_OLD_RDN_ATTR_IS_NO_USER_MOD;
+                op.appendErrorMessage(getMessage(msgID, 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);
+
+                int msgID = MSGID_MODDN_NEW_RDN_ATTR_IS_NO_USER_MOD;
+                op.appendErrorMessage(getMessage(msgID, 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()) )
+        {
+          StringBuilder invalidReason = new StringBuilder();
+          if (! newEntry.conformsToSchema(null, false, true, true,
+                                          invalidReason))
+          {
+            op.setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
+            op.appendErrorMessage(getMessage(MSGID_MODDN_VIOLATES_SCHEMA,
+                                          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.CONSTRAINT_VIOLATION);
+              op.appendErrorMessage(getMessage(
+                                            MSGID_MODDN_NEWRDN_ATTR_IS_OBSOLETE,
+                                            String.valueOf(entryDN),
+                                            at.getNameOrOID()));
+              break modifyDNProcessing;
+            }
+          }
+        }
+
+
+        // Check for and handle a request to cancel this operation.
+        if (op.getCancelRequest() != null)
+        {
+          op.indicateCancelled(op.getCancelRequest());
+          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);
+
+            int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
+            op.appendErrorMessage(getMessage(msgID));
+            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);
+
+                  int msgID = MSGID_MODDN_PREOP_INCREMENT_NO_ATTR;
+                  op.appendErrorMessage(getMessage(msgID,
+                                                String.valueOf(entryDN),
+                                                a.getName()));
+
+                  break modifyDNProcessing;
+                }
+                else if (attrList.size() > 1)
+                {
+                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+                  int msgID = MSGID_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES;
+                  op.appendErrorMessage(getMessage(msgID,
+                                                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);
+
+                  int msgID = MSGID_MODDN_PREOP_INCREMENT_NO_ATTR;
+                  op.appendErrorMessage(getMessage(msgID,
+                                                String.valueOf(entryDN),
+                                                a.getName()));
+
+                  break modifyDNProcessing;
+                }
+                else if (values.size() > 1)
+                {
+                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+                  int msgID = MSGID_MODDN_PREOP_INCREMENT_MULTIPLE_VALUES;
+                  op.appendErrorMessage(getMessage(msgID,
+                                                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);
+
+                  int msgID = MSGID_MODDN_PREOP_INCREMENT_VALUE_NOT_INTEGER;
+                  op.appendErrorMessage(getMessage(msgID,
+                                                String.valueOf(entryDN),
+                                                a.getName()));
+
+                  break modifyDNProcessing;
+                }
+
+                LinkedHashSet<AttributeValue> newValues = a.getValues();
+                if ((newValues == null) || newValues.isEmpty())
+                {
+                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+                  int msgID = MSGID_MODDN_PREOP_INCREMENT_NO_AMOUNT;
+                  op.appendErrorMessage(getMessage(msgID,
+                                                String.valueOf(entryDN),
+                                                a.getName()));
+
+                  break modifyDNProcessing;
+                }
+                else if (newValues.size() > 1)
+                {
+                  op.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+
+                  int msgID = MSGID_MODDN_PREOP_INCREMENT_MULTIPLE_AMOUNTS;
+                  op.appendErrorMessage(getMessage(msgID,
+                                                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);
+
+                  int msgID = MSGID_MODDN_PREOP_INCREMENT_AMOUNT_NOT_INTEGER;
+                  op.appendErrorMessage(getMessage(msgID,
+                                                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())
+          {
+            StringBuilder invalidReason = new StringBuilder();
+            if (! newEntry.conformsToSchema(null, false, true, true,
+                                            invalidReason))
+            {
+              op.setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
+
+              op.appendErrorMessage(getMessage(
+                                            MSGID_MODDN_PREOP_VIOLATES_SCHEMA,
+                                            String.valueOf(entryDN),
+                                            String.valueOf(invalidReason)));
+              break modifyDNProcessing;
+            }
+          }
+        }
+
+
+        // Check for and handle a request to cancel this operation.
+        if (op.getCancelRequest() != null)
+        {
+          op.indicateCancelled(op.getCancelRequest());
+          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(getMessage(MSGID_MODDN_SERVER_READONLY,
+                                              String.valueOf(entryDN)));
+                break modifyDNProcessing;
+
+              case INTERNAL_ONLY:
+                if (! (op.isInternalOperation() ||
+                    op.isSynchronizationOperation()))
+                {
+                  op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+                  op.appendErrorMessage(getMessage(MSGID_MODDN_SERVER_READONLY,
+                                                String.valueOf(entryDN)));
+                  break modifyDNProcessing;
+                }
+            }
+
+            switch (currentBackend.getWritabilityMode())
+            {
+              case DISABLED:
+                op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+                op.appendErrorMessage(getMessage(MSGID_MODDN_BACKEND_READONLY,
+                                              String.valueOf(entryDN)));
+                break modifyDNProcessing;
+
+              case INTERNAL_ONLY:
+                if (! (op.isInternalOperation() ||
+                    op.isSynchronizationOperation()))
+                {
+                  op.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+                  op.appendErrorMessage(getMessage(MSGID_MODDN_BACKEND_READONLY,
+                                                String.valueOf(entryDN)));
+                  break modifyDNProcessing;
+                }
+            }
+          }
+
+
+          if (noOp)
+          {
+            op.appendErrorMessage(getMessage(MSGID_MODDN_NOOP));
+
+            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(ErrorLogCategory.SYNCHRONIZATION,
+                         ErrorLogSeverity.SEVERE_ERROR,
+                         MSGID_MODDN_SYNCH_PREOP_FAILED, 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.getErrorMessage());
+          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());
+
+          String message = coe.getMessage();
+          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(ErrorLogCategory.SYNCHRONIZATION,
+                     ErrorLogSeverity.SEVERE_ERROR,
+                     MSGID_MODDN_SYNCH_POSTOP_FAILED, 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 modify DN plugins.
+    if (! skipPostOperation)
+    {
+      PostOperationPluginResult postOperationResult =
+           pluginConfigManager.invokePostOperationModifyDNPlugins(op);
+      if (postOperationResult.connectionTerminated())
+      {
+        op.setResultCode(ResultCode.CANCELED);
+
+        int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
+        op.appendErrorMessage(getMessage(msgID));
+        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);
+          }
+
+          int    msgID   = MSGID_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER;
+          String message = getMessage(msgID, getExceptionMessage(e));
+          logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
+                   message, msgID);
+        }
+      }
+    }
+
+  }
+
 
   /**
    * Attaches the current local operation to the global operation so that

--
Gitblit v1.10.0