From e5e8ceb274a2795ec70cf8cc771a247e2620a30f Mon Sep 17 00:00:00 2001
From: jarnou <jarnou@localhost>
Date: Tue, 03 Jul 2007 09:29:17 +0000
Subject: [PATCH] Commits the refactoring of the core server to provide support for proxy/distribution/virtual functionnalities. This includes the new set of local operations, as well as the workflow and networkgroup support.

---
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ModifyMsg.java                                                 |    3 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java                        |   41 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/NetworkGroupTest.java                                  |  470 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java            |   21 
 opendj-sdk/opends/src/server/org/opends/server/api/SynchronizationProvider.java                                                    |   30 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java       |   11 
 opendj-sdk/opends/src/server/org/opends/server/core/WorkflowImpl.java                                                              |  127 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java    |   27 
 opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java                                                  |    4 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidatorTestCase.java   |   36 
 opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroup.java                                                              |  407 
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyDNOperation.java                                                         |   83 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java                                          |   10 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AbandonOperationTestCase.java                          |   20 
 opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupNamingContexts.java                                                |  157 
 opendj-sdk/opends/src/server/org/opends/server/core/AddOperationWrapper.java                                                       |  653 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentServerStateTest.java           |    5 
 opendj-sdk/opends/src/server/org/opends/server/core/BindOperationBasis.java                                                        |  959 ++
 opendj-sdk/opends/src/server/org/opends/server/core/ExtendedOperation.java                                                         |   79 
 opendj-sdk/opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java                                       |    7 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidatorTestCase.java |   34 
 opendj-sdk/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java                                              |   18 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ProtocolWindowTest.java                         |    7 
 opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java                                                          |   12 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentStateTest.java                 |    5 
 opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperationBasis.java                                                      |  671 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SimilarityBasedPasswordValidatorTestCase.java    |   10 
 opendj-sdk/opends/src/server/org/opends/server/core/AbandonOperation.java                                                          |   86 
 opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java                                                             | 2244 ----
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java                                                 |    3 
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java                                                           | 2933 ------
 opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupCriteria.java                                                      |   46 
 opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java                                      |   23 
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java                             |   19 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java                         |   14 
 opendj-sdk/opends/src/server/org/opends/server/core/WorkflowTopologyNode.java                                                      |  535 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/WorkflowTopologyTest.java                              |  938 ++
 opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationWrapper.java                                                    |  903 ++
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LengthBasedPasswordValidatorTestCase.java        |   18 
 opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java                                                          |   19 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/InitOnLineTest.java                             |   25 
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperationWrapper.java                                                    |  622 +
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java                                          |    4 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java         |   13 
 opendj-sdk/opends/src/server/org/opends/server/api/AccessControlHandler.java                                                       |   30 
 opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java                                              |   30 
 opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java                                                           |   35 
 opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java                                                           |  126 
 opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java                                                 |    4 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxTestCase.java                              |    4 
 opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java                                                          |   81 
 opendj-sdk/opends/src/server/org/opends/server/types/Operation.java                                                                |  553 -
 opendj-sdk/opends/src/server/org/opends/server/workflowelement/LeafWorkflowElement.java                                            |   40 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/StressTest.java                                 |    6 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AddOperationTestCase.java                              |   41 
 opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java                                           |   12 
 opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperationWrapper.java                                                    |  569 +
 opendj-sdk/opends/src/server/org/opends/server/core/PluginConfigManager.java                                                       |  228 
 opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperation.java                                                           | 1385 ---
 opendj-sdk/opends/src/server/org/opends/server/types/AbstractOperation.java                                                        |  691 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java                        |   62 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java     |    6 
 opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkerThread.java                                             |    9 
 opendj-sdk/opends/src/server/org/opends/server/core/WorkflowResultCode.java                                                        |  249 
 opendj-sdk/opends/src/server/org/opends/server/core/Workflow.java                                                                  |   73 
 opendj-sdk/opends/src/server/org/opends/server/core/GroupManager.java                                                              |    6 
 opendj-sdk/opends/src/server/org/opends/server/core/UnbindOperation.java                                                           |   68 
 opendj-sdk/opends/src/server/org/opends/server/core/RootDseWorkflowTopology.java                                                   |  175 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ServerSideSortControlTestCase.java                 |   30 
 opendj-sdk/opends/src/server/org/opends/server/core/BindOperationWrapper.java                                                      |  691 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java                           |    4 
 opendj-sdk/opends/src/server/org/opends/server/extensions/StaticGroup.java                                                         |   10 
 opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java                                                      | 1759 +++
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java                  |    7 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java     |   10 
 opendj-sdk/opends/src/server/org/opends/server/core/AddOperationBasis.java                                                         | 1024 ++
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java                            |   36 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java                      |    5 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java       |   14 
 opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java                                            |   87 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReSyncTest.java                                 |    8 
 opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupPolicy.java                                                        |   46 
 opendj-sdk/opends/src/server/org/opends/server/config/JMXMBean.java                                                                |    4 
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperationBasis.java                                                      |  773 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java                           |   64 
 opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java                                     |   25 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java                                |   62 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/UpdateComparatorTest.java                |    3 
 opendj-sdk/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java                                                  |   19 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java                           |  130 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java                             |   87 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java  |    5 
 opendj-sdk/opends/src/server/org/opends/server/core/WorkflowTopology.java                                                          |  135 
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java                                    |    5 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java                           |   28 
 opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java                                                |   35 
 opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java                                            |   19 
 opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java                                                              | 2516 -----
 opendj-sdk/opends/src/server/org/opends/server/workflowelement/WorkflowElement.java                                                |   81 
 opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java                                    |  107 
 opendj-sdk/opends/src/server/org/opends/server/api/WorkQueue.java                                                                  |    4 
 opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java                                                    |    3 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java                           |    9 
 opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java                                                           | 2310 ----
 104 files changed, 14,465 insertions(+), 12,525 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/AccessControlHandler.java b/opendj-sdk/opends/src/server/org/opends/server/api/AccessControlHandler.java
index 0639d14..be102f9 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/AccessControlHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/api/AccessControlHandler.java
@@ -27,9 +27,9 @@
 package org.opends.server.api;
 
 
-
 import org.opends.server.core.*;
 import org.opends.server.types.*;
+import org.opends.server.workflowelement.localbackend.*;
 
 
 /**
@@ -53,7 +53,8 @@
    *          the access control configuration, or <CODE>false</CODE>
    *          if not.
    */
-  public abstract boolean isAllowed(AddOperation addOperation);
+  public abstract boolean isAllowed(LocalBackendAddOperation
+      addOperation);
 
 
 
@@ -69,7 +70,8 @@
    *          the access control configuration, or <CODE>false</CODE>
    *          if not.
    */
-  public abstract boolean isAllowed(BindOperation bindOperation);
+  public abstract boolean isAllowed(LocalBackendBindOperation
+      bindOperation);
 
 
 
@@ -102,7 +104,8 @@
    *          the access control configuration, or <CODE>false</CODE>
    *          if not.
    */
-  public abstract boolean isAllowed(DeleteOperation deleteOperation);
+  public abstract boolean isAllowed(LocalBackendDeleteOperation
+      deleteOperation);
 
 
 
@@ -135,7 +138,8 @@
    *          the access control configuration, or <CODE>false</CODE>
    *          if not.
    */
-  public abstract boolean isAllowed(ModifyOperation modifyOperation);
+  public abstract boolean isAllowed(LocalBackendModifyOperation
+      modifyOperation);
 
 
 
@@ -171,7 +175,8 @@
    *          the access control configuration, or <CODE>false</CODE>
    *          if not.
    */
-  public abstract boolean isAllowed(SearchOperation searchOperation);
+  public abstract boolean isAllowed(LocalBackendSearchOperation
+      searchOperation);
 
 
 
@@ -189,8 +194,9 @@
    *          the access control configuration, or <CODE>false</CODE>
    *          if not.
    */
-  public abstract boolean maySend(SearchOperation searchOperation,
-                                  SearchResultEntry searchEntry);
+  public abstract boolean maySend(
+                         SearchOperation searchOperation,
+                         SearchResultEntry searchEntry);
 
 
 
@@ -207,7 +213,8 @@
    *          removed.
    */
   public abstract SearchResultEntry filterEntry(
-      SearchOperation searchOperation, SearchResultEntry searchEntry);
+                         SearchOperation searchOperation,
+                         SearchResultEntry searchEntry);
 
 
 
@@ -225,8 +232,9 @@
    *         the access control configuration, or <CODE>false</CODE>
    *         if not.
    */
-  public abstract boolean maySend(SearchOperation searchOperation,
-                               SearchResultReference searchReference);
+  public abstract boolean maySend(
+                          SearchOperation searchOperation,
+                          SearchResultReference searchReference);
 
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
index 46475d5..cacc625 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
@@ -41,6 +41,8 @@
 import org.opends.server.core.PersistentSearch;
 import org.opends.server.core.PluginConfigManager;
 import org.opends.server.core.SearchOperation;
+import org.opends.server.core.NetworkGroup;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValue;
@@ -117,6 +119,9 @@
   // A set of persistent searches registered for this client.
   private CopyOnWriteArrayList<PersistentSearch> persistentSearches;
 
+  // The network group to which the connection belongs to.
+  private NetworkGroup networkGroup;
+
 
 
   /**
@@ -136,6 +141,7 @@
     lookthroughLimit   = DirectoryServer.getLookthroughLimit();
     finalized          = false;
     privileges         = new HashSet<Privilege>();
+    networkGroup       = NetworkGroup.getDefaultNetworkGroup();
   }
 
 
@@ -653,7 +659,8 @@
    * @return  The set of operations in progress for this client
    *          connection.
    */
-  public abstract Collection<Operation> getOperationsInProgress();
+  public abstract Collection<AbstractOperation>
+                                      getOperationsInProgress();
 
 
 
@@ -667,7 +674,8 @@
    *          or <CODE>null</CODE> if no such operation could be
    *          found.
    */
-  public abstract Operation getOperationInProgress(int messageID);
+  public abstract AbstractOperation
+                          getOperationInProgress(int messageID);
 
 
 
@@ -1542,5 +1550,28 @@
   {
     finalizeConnectionInternal();
   }
+
+
+  /**
+   * Returns the network group to which the connection belongs.
+   *
+   * @return the network group attached to the connection
+   */
+  public NetworkGroup getNetworkGroup()
+  {
+    return networkGroup;
+  }
+
+  /**
+   * Sets the network group to which the connection belongs.
+   *
+   * @param networkGroup  the network group to which the
+   *                      connections belongs to
+   */
+  public void setNetworkGroup (NetworkGroup networkGroup)
+  {
+    this.networkGroup = networkGroup;
+  }
+
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/SynchronizationProvider.java b/opendj-sdk/opends/src/server/org/opends/server/api/SynchronizationProvider.java
index 2fb1657..01bb2a1 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/SynchronizationProvider.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/api/SynchronizationProvider.java
@@ -32,14 +32,12 @@
 
 import org.opends.server.admin.std.server.SynchronizationProviderCfg;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.AddOperation;
-import org.opends.server.core.DeleteOperation;
-import org.opends.server.core.ModifyOperation;
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.Modification;
 import org.opends.server.types.SynchronizationProviderResult;
+import org.opends.server.workflowelement.localbackend.*;
 
 
 
@@ -109,7 +107,8 @@
    *                              synchronization processing.
    */
   public SynchronizationProviderResult
-              handleConflictResolution(AddOperation addOperation)
+              handleConflictResolution(LocalBackendAddOperation
+                                                        addOperation)
          throws DirectoryException
   {
     // No processing is required by default.
@@ -136,7 +135,8 @@
    *                              synchronization processing.
    */
   public abstract SynchronizationProviderResult
-                       doPreOperation(AddOperation addOperation)
+                       doPreOperation(LocalBackendAddOperation
+                                                     addOperation)
          throws DirectoryException;
 
 
@@ -153,7 +153,8 @@
    * @throws  DirectoryException  If a problem occurs during
    *                              synchronization processing.
    */
-  public abstract void doPostOperation(AddOperation addOperation)
+  public abstract void doPostOperation(LocalBackendAddOperation
+                                                      addOperation)
          throws DirectoryException;
 
 
@@ -177,7 +178,8 @@
    *                              synchronization processing.
    */
   public SynchronizationProviderResult
-       handleConflictResolution(DeleteOperation deleteOperation)
+       handleConflictResolution(LocalBackendDeleteOperation
+                                                 deleteOperation)
          throws DirectoryException
   {
     // No processing is required by default.
@@ -204,7 +206,8 @@
    *                              synchronization processing.
    */
   public abstract SynchronizationProviderResult
-              doPreOperation(DeleteOperation deleteOperation)
+              doPreOperation(LocalBackendDeleteOperation
+                                              deleteOperation)
          throws DirectoryException;
 
 
@@ -222,7 +225,8 @@
    *                              synchronization processing.
    */
   public abstract void doPostOperation(
-                            DeleteOperation deleteOperation)
+                            LocalBackendDeleteOperation
+                                                deleteOperation)
          throws DirectoryException;
 
 
@@ -246,7 +250,7 @@
    *                              synchronization processing.
    */
   public SynchronizationProviderResult
-              handleConflictResolution(ModifyOperation
+              handleConflictResolution(LocalBackendModifyOperation
                                             modifyOperation)
          throws DirectoryException
   {
@@ -274,7 +278,8 @@
    *                              synchronization processing.
    */
   public abstract SynchronizationProviderResult
-                       doPreOperation(ModifyOperation modifyOperation)
+                       doPreOperation(LocalBackendModifyOperation
+                                                     modifyOperation)
          throws DirectoryException;
 
 
@@ -292,7 +297,8 @@
    *                              synchronization processing.
    */
   public abstract void doPostOperation(
-                            ModifyOperation modifyOperation)
+                            LocalBackendModifyOperation
+                                            modifyOperation)
          throws DirectoryException;
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/WorkQueue.java b/opendj-sdk/opends/src/server/org/opends/server/api/WorkQueue.java
index 55b61d2..7375b5c 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/WorkQueue.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/api/WorkQueue.java
@@ -30,9 +30,9 @@
 
 import org.opends.server.admin.std.server.WorkQueueCfg;
 import org.opends.server.config.ConfigException;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.InitializationException;
-import org.opends.server.types.Operation;
 
 
 
@@ -95,7 +95,7 @@
    *                              already has too many pending
    *                              requests in the queue).
    */
-  public abstract void submitOperation(Operation operation)
+  public abstract void submitOperation(AbstractOperation operation)
          throws DirectoryException;
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java
index c2126da..06e9a8f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java
@@ -30,8 +30,8 @@
 import org.opends.server.types.*;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.Group;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.api.ConnectionSecurityProvider;
-import org.opends.server.core.AddOperation;
 import org.opends.server.core.SearchOperation;
 import org.opends.server.extensions.TLSConnectionSecurityProvider;
 import org.opends.server.types.Operation;
@@ -250,7 +250,7 @@
       this.resourceEntry=entry;
       this.operation=operation;
       this.clientConnection=operation.getClientConnection();
-      if(operation instanceof AddOperation)
+      if(operation instanceof AddOperationBasis)
           this.isAddOp=true;
 
       //If the proxied authorization control was processed, then the operation
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
index e934089..ab0ad50 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
@@ -27,11 +27,14 @@
 
 package org.opends.server.authorization.dseecompat;
 
+
+import static org.opends.server.authorization.dseecompat.Aci.*;
+
 import org.opends.server.admin.std.server.DseeCompatAccessControlHandlerCfg;
 import org.opends.server.api.AccessControlHandler;
-import static org.opends.server.authorization.dseecompat.Aci.*;
 import static org.opends.server.config.ConfigConstants.ATTR_AUTHZ_GLOBAL_ACI;
 import org.opends.server.core.*;
+
 import static org.opends.server.loggers.ErrorLogger.logError;
 import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
 import static org.opends.server.loggers.debug.DebugLogger.getTracer;
@@ -49,6 +52,8 @@
 import java.util.*;
 import java.util.concurrent.locks.Lock;
 
+import org.opends.server.workflowelement.localbackend.*;
+
 /**
  * The AciHandler class performs the main processing for the
  * dseecompat package.
@@ -259,7 +264,7 @@
      * @return  True if access is allowed.
      */
     private boolean aciCheckMods(AciLDAPOperationContainer container,
-                                 ModifyOperation operation,
+                                 LocalBackendModifyOperation operation,
                                  boolean skipAccessCheck) {
         Entry resourceEntry=container.getResourceEntry();
         DN dn=resourceEntry.getDN();
@@ -833,7 +838,7 @@
      * @param operation The add operation to check access on.
      * @return  True if access is allowed.
      */
-    public boolean isAllowed(AddOperation operation) {
+    public boolean isAllowed(LocalBackendAddOperation operation) {
         AciLDAPOperationContainer operationContainer =
                 new AciLDAPOperationContainer(operation, ACI_ADD);
         boolean ret=isAllowed(operationContainer,operation);
@@ -883,7 +888,7 @@
      * @param operation The delete operation to check access on.
      * @return  True if access is allowed.
      */
-   public boolean isAllowed(DeleteOperation operation) {
+   public boolean isAllowed(LocalBackendDeleteOperation operation) {
        AciLDAPOperationContainer operationContainer=
                new AciLDAPOperationContainer(operation, ACI_DELETE);
        return isAllowed(operationContainer, operation);
@@ -896,7 +901,7 @@
     * @return  True if access is allowed.
     */
 
-  public boolean isAllowed(ModifyOperation operation) {
+  public boolean isAllowed(LocalBackendModifyOperation operation) {
       AciLDAPOperationContainer operationContainer=
               new AciLDAPOperationContainer(operation, ACI_NULL);
       return aciCheckMods(operationContainer, operation,
@@ -1169,7 +1174,7 @@
    * {@inheritDoc}
    */
   @Override
-  public boolean isAllowed(BindOperation bindOperation) {
+  public boolean isAllowed(LocalBackendBindOperation bindOperation) {
       //Not planned to be implemented.
       return true;
   }
@@ -1187,7 +1192,7 @@
    * {@inheritDoc}
    */
   @Override
-  public boolean isAllowed(SearchOperation searchOperation) {
+  public boolean isAllowed(LocalBackendSearchOperation searchOperation) {
       //Not planned to be implemented.
       return true;
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
index cf1898d..2f791e4 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
@@ -33,6 +33,7 @@
 import org.opends.server.types.Modification;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.Entry;
+import org.opends.server.workflowelement.localbackend.*;
 
 /**
  * The AciLDAPOperationContainer is an AciContainer
@@ -66,7 +67,9 @@
      * @param operation The add operation to evaluate.
      * @param rights  The rights of an add operation.
      */
-    public AciLDAPOperationContainer(AddOperation operation, int rights) {
+    public AciLDAPOperationContainer(LocalBackendAddOperation operation,
+        int rights)
+    {
         super(operation, rights, operation.getEntryToAdd());
     }
 
@@ -75,7 +78,9 @@
      * @param operation The add operation to evaluate.
      * @param rights  The rights of a delete operation.
      */
-    public AciLDAPOperationContainer(DeleteOperation operation,  int rights) {
+    public AciLDAPOperationContainer(LocalBackendDeleteOperation operation,
+        int rights)
+    {
         super(operation, rights, operation.getEntryToDelete());
     }
 
@@ -84,7 +89,9 @@
      * @param rights The rights of modify operation.
      * @param operation The add operation to evaluate.
      */
-    public AciLDAPOperationContainer(ModifyOperation operation, int rights) {
+    public AciLDAPOperationContainer(LocalBackendModifyOperation operation,
+        int rights)
+    {
         super(operation, rights, operation.getCurrentEntry());
         this.modifications=operation.getModifications();
     }
@@ -106,8 +113,10 @@
      * @param rights The rights of a search operation.
      * @param entry The entry to be evaluated for this search.
      */
-    public AciLDAPOperationContainer(SearchOperation operation,  int rights,
-                                     SearchResultEntry entry) {
+    public AciLDAPOperationContainer(SearchOperation operation,
+        int rights,
+        SearchResultEntry entry)
+    {
         super(operation, rights,  entry);
         this.searchEntry = entry;
     }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
index 4e1e10b..5cc1340 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
@@ -27,6 +27,7 @@
 
 package org.opends.server.authorization.dseecompat;
 
+import org.opends.server.workflowelement.localbackend.*;
 import org.opends.server.api.ChangeNotificationListener;
 import org.opends.server.api.BackendInitializationListener;
 import org.opends.server.api.Backend;
@@ -235,8 +236,10 @@
                   null, baseDN, SearchScope.WHOLE_SUBTREE,
                   DereferencePolicy.NEVER_DEREF_ALIASES,
                   0, 0, false, aciFilter, attrs, null);
+        LocalBackendSearchOperation localInternalSearch =
+          new LocalBackendSearchOperation(internalSearch);
         try  {
-          backend.search(internalSearch);
+          backend.search(localInternalSearch);
         } catch (Exception e) {
             if (debugEnabled())
             {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/config/JMXMBean.java b/opendj-sdk/opends/src/server/org/opends/server/config/JMXMBean.java
index 25a8f44..1754572 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/config/JMXMBean.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/config/JMXMBean.java
@@ -82,7 +82,7 @@
 import org.opends.server.protocols.ldap.LDAPModification;
 import org.opends.server.protocols.ldap.LDAPAttribute ;
 import org.opends.server.protocols.internal.InternalSearchOperation ;
-import org.opends.server.core.ModifyOperation ;
+import org.opends.server.core.ModifyOperationBasis ;
 import org.opends.server.types.LDAPException;
 import org.opends.server.types.ModificationType;
 
@@ -828,7 +828,7 @@
 
     //
     // Process the modify
-    ModifyOperation op = jmxClientConnection.processModify(
+    ModifyOperationBasis op = jmxClientConnection.processModify(
           new ASN1OctetString(configEntryDN.toString()),
           ldapModList);
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/AbandonOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/AbandonOperation.java
index fdcefa8..cf2d828 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/AbandonOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/AbandonOperation.java
@@ -28,26 +28,30 @@
 
 
 
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ERROR_MESSAGE;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ID_TO_ABANDON;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_PROCESSING_TIME;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
+import static org.opends.server.messages.CoreMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+
 import java.util.List;
 
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.plugin.PreParsePluginResult;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.CancelRequest;
 import org.opends.server.types.CancelResult;
 import org.opends.server.types.Control;
 import org.opends.server.types.DisconnectReason;
-import org.opends.server.types.Operation;
 import org.opends.server.types.OperationType;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.operation.PostOperationAbandonOperation;
 import org.opends.server.types.operation.PreParseAbandonOperation;
 
-import static org.opends.server.core.CoreConstants.*;
 import static org.opends.server.loggers.AccessLogger.*;
-import static org.opends.server.messages.CoreMessages.*;
-import static org.opends.server.messages.MessageHandler.*;
-
-
 
 
 /**
@@ -55,23 +59,18 @@
  * may already be in progress in the Directory Server.
  */
 public class AbandonOperation
-       extends Operation
+       extends AbstractOperation
        implements PreParseAbandonOperation, PostOperationAbandonOperation
 {
 
-
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
 
   // The message ID of the operation that should be abandoned.
   private final int idToAbandon;
 
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
-
-
   /**
    * Creates a new abandon operation with the provided information.
    *
@@ -106,41 +105,6 @@
     return idToAbandon;
   }
 
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
-
   /**
    * {@inheritDoc}
    */
@@ -213,7 +177,7 @@
     }
 
     String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
+         String.valueOf(getProcessingTime());
 
     return new String[][]
     {
@@ -261,9 +225,12 @@
 
 
   /**
-   * {@inheritDoc}
+   * Performs the work of actually processing this operation.  This
+   * should include all processing for the operation, including
+   * invoking plugins, logging messages, performing access control,
+   * managing synchronization, and any other work that might need to
+   * be done in the course of processing.
    */
-  @Override()
   public final void run()
   {
     setResultCode(ResultCode.UNDEFINED);
@@ -276,7 +243,7 @@
 
 
     // Start the processing timer.
-    processingStartTime = System.currentTimeMillis();
+    setProcessingStartTime();
 
 
     // Create a labeled block of code that we can break out of if a problem is
@@ -295,7 +262,7 @@
         int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
         appendErrorMessage(getMessage(msgID));
 
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
 
         logAbandonRequest(this);
         logAbandonResult(this);
@@ -321,7 +288,7 @@
       // code to reflect whether the abandon was successful and an error message
       // if it was not.  Even though there is no response, the result should
       // still be logged.
-      Operation operation =
+      AbstractOperation operation =
            clientConnection.getOperationInProgress(idToAbandon);
       if (operation == null)
       {
@@ -356,7 +323,7 @@
 
 
     // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
+    setProcessingStopTime();
 
 
     // Log the result of the abandon operation.
@@ -392,7 +359,7 @@
    * {@inheritDoc}
    */
   @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
+  public boolean setCancelRequest(CancelRequest cancelRequest)
   {
     // Abandon operations cannot be canceled.
     return false;
@@ -414,5 +381,6 @@
     buffer.append(idToAbandon);
     buffer.append(")");
   }
+
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java
index 8b265d1..a81b672 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java
@@ -26,248 +26,23 @@
  */
 package org.opends.server.core;
 
-
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.locks.Lock;
 
-import org.opends.server.api.AttributeSyntax;
-import org.opends.server.api.Backend;
-import org.opends.server.api.ChangeNotificationListener;
-import org.opends.server.api.ClientConnection;
-import org.opends.server.api.PasswordStorageScheme;
-import org.opends.server.api.PasswordValidator;
-import org.opends.server.api.SynchronizationProvider;
-import org.opends.server.api.plugin.PostOperationPluginResult;
-import org.opends.server.api.plugin.PreOperationPluginResult;
-import org.opends.server.api.plugin.PreParsePluginResult;
-import org.opends.server.controls.LDAPAssertionRequestControl;
-import org.opends.server.controls.LDAPPostReadRequestControl;
-import org.opends.server.controls.LDAPPostReadResponseControl;
-import org.opends.server.controls.ProxiedAuthV1Control;
-import org.opends.server.controls.ProxiedAuthV2Control;
-import org.opends.server.schema.AuthPasswordSyntax;
-import org.opends.server.schema.BooleanSyntax;
-import org.opends.server.schema.UserPasswordSyntax;
-import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.protocols.ldap.LDAPAttribute;
 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.CancelRequest;
-import org.opends.server.types.CancelResult;
-import org.opends.server.types.Control;
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.DN;
-import org.opends.server.types.Entry;
-import org.opends.server.types.ErrorLogCategory;
-import org.opends.server.types.ErrorLogSeverity;
-import org.opends.server.types.LDAPException;
-import org.opends.server.types.LockManager;
 import org.opends.server.types.ObjectClass;
 import org.opends.server.types.Operation;
-import org.opends.server.types.OperationType;
-import org.opends.server.types.Privilege;
 import org.opends.server.types.RawAttribute;
-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.PostOperationAddOperation;
-import org.opends.server.types.operation.PostResponseAddOperation;
-import org.opends.server.types.operation.PreOperationAddOperation;
-import org.opends.server.types.operation.PreParseAddOperation;
-import org.opends.server.util.TimeThread;
-
-import static org.opends.server.config.ConfigConstants.*;
-import static org.opends.server.core.CoreConstants.*;
-import static org.opends.server.loggers.AccessLogger.*;
-import org.opends.server.types.DebugLogLevel;
-import static org.opends.server.loggers.ErrorLogger.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
-import static org.opends.server.messages.CoreMessages.*;
-import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
-
-
 
 /**
- * This class defines an operation that may be used to add a new entry to the
- * Directory Server.
+ * This interface defines an operation that may be used to add a new entry to
+ * the Directory Server.
  */
-public class AddOperation
-       extends Operation
-       implements PreParseAddOperation, PreOperationAddOperation,
-                  PostOperationAddOperation, PostResponseAddOperation
+public interface AddOperation extends Operation
 {
-  /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = getTracer();
-
-  // The set of response controls to send to the client.
-  private ArrayList<Control> responseControls;
-
-  // The raw, unprocessed entry DN as provided in the request.  This may or may
-  // not be a valid DN.
-  private ByteString rawEntryDN;
-
-  // The cancel request that has been issued for this add operation.
-  private CancelRequest cancelRequest;
-
-  // The processed DN of the entry to add.
-  private DN entryDN;
-
-  // The proxied authorization target DN for this operation.
-  private DN proxiedAuthorizationDN;
-
-  // The entry being added to the server.
-  private Entry entry;
-
-  // The set of attributes (including the objectclass attribute) in a raw,
-  // unprocessed form as provided in the request.  One or more of these
-  // attributes may be invalid.
-  private List<RawAttribute> rawAttributes;
-
-  // The set of operational attributes for the entry to add.
-  private Map<AttributeType,List<Attribute>> operationalAttributes;
-
-  // The set of user attributes for the entry to add.
-  private Map<AttributeType,List<Attribute>> userAttributes;
-
-  // The set of objectclasses for the entry to add.
-  private Map<ObjectClass,String> objectClasses;
-
-  // The change number that has been assigned to this operation.
-  private long changeNumber;
-
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
-
-
-  /**
-   * Creates a new add operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  rawEntryDN        The raw DN of the entry to add from the client
-   *                           request.  This may or may not be a valid DN.
-   * @param  rawAttributes     The raw set of attributes from the client
-   *                           request (including the objectclass attribute).
-   *                           This may contain invalid attributes.
-   */
-  public AddOperation(ClientConnection clientConnection, long operationID,
-                      int messageID, List<Control> requestControls,
-                      ByteString rawEntryDN, List<RawAttribute> rawAttributes)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.rawEntryDN    = rawEntryDN;
-    this.rawAttributes = rawAttributes;
-
-    responseControls       = new ArrayList<Control>();
-    cancelRequest          = null;
-    entry                  = null;
-    entryDN                = null;
-    userAttributes         = null;
-    operationalAttributes  = null;
-    objectClasses          = null;
-    proxiedAuthorizationDN = null;
-    changeNumber           = -1;
-  }
-
-
-
-  /**
-   * Creates a new add operation with the provided information.
-   *
-   * @param  clientConnection       The client connection with which this
-   *                                operation is associated.
-   * @param  operationID            The operation ID for this operation.
-   * @param  messageID              The message ID of the request with which
-   *                                this operation is associated.
-   * @param  requestControls        The set of controls included in the request.
-   * @param  entryDN                The DN for the entry.
-   * @param  objectClasses          The set of objectclasses for the entry.
-   * @param  userAttributes         The set of user attributes for the entry.
-   * @param  operationalAttributes  The set of operational attributes for the
-   *                                entry.
-   */
-  public AddOperation(ClientConnection clientConnection, long operationID,
-                      int messageID, List<Control> requestControls,
-                      DN entryDN, Map<ObjectClass,String> objectClasses,
-                      Map<AttributeType,List<Attribute>> userAttributes,
-                      Map<AttributeType,List<Attribute>> operationalAttributes)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.entryDN               = entryDN;
-    this.objectClasses         = objectClasses;
-    this.userAttributes        = userAttributes;
-    this.operationalAttributes = operationalAttributes;
-
-    entry = null;
-
-    rawEntryDN = new ASN1OctetString(entryDN.toString());
-
-    rawAttributes = new ArrayList<RawAttribute>();
-
-    ArrayList<ASN1OctetString> ocValues = new ArrayList<ASN1OctetString>();
-    for (String s : objectClasses.values())
-    {
-      ocValues.add(new ASN1OctetString(s));
-    }
-
-    LDAPAttribute ocAttr = new LDAPAttribute(ATTR_OBJECTCLASS, ocValues);
-    rawAttributes.add(ocAttr);
-
-    for (List<Attribute> attrList : userAttributes.values())
-    {
-      for (Attribute a : attrList)
-      {
-        rawAttributes.add(new LDAPAttribute(a));
-      }
-    }
-
-    for (List<Attribute> attrList : operationalAttributes.values())
-    {
-      for (Attribute a : attrList)
-      {
-        rawAttributes.add(new LDAPAttribute(a));
-      }
-    }
-
-    responseControls       = new ArrayList<Control>();
-    proxiedAuthorizationDN = null;
-    cancelRequest          = null;
-    changeNumber           = -1;
-  }
-
-
 
   /**
    * Retrieves the DN of the entry to add in a raw, unparsed form as it was
@@ -276,12 +51,7 @@
    *
    * @return  The DN of the entry in a raw, unparsed form.
    */
-  public final ByteString getRawEntryDN()
-  {
-    return rawEntryDN;
-  }
-
-
+  public abstract ByteString getRawEntryDN();
 
   /**
    * Specifies the raw entry DN for the entry to add.  This should only be
@@ -292,14 +62,7 @@
    *
    * @param  rawEntryDN  The raw entry DN for the entry to add.
    */
-  public final void setRawEntryDN(ByteString rawEntryDN)
-  {
-    this.rawEntryDN = rawEntryDN;
-
-    entryDN = null;
-  }
-
-
+  public abstract void setRawEntryDN(ByteString rawEntryDN);
 
   /**
    * Retrieves the DN of the entry to add.  This method should not be called
@@ -309,12 +72,7 @@
    * @return  The DN of the entry to add, or <CODE>null</CODE> if it has not yet
    *          been parsed from the raw DN.
    */
-  public final DN getEntryDN()
-  {
-    return entryDN;
-  }
-
-
+  public abstract DN getEntryDN();
 
   /**
    * Retrieves the set of attributes in their raw, unparsed form as read from
@@ -325,12 +83,7 @@
    * @return  The set of attributes in their raw, unparsed form as read from the
    *          client request.
    */
-  public final List<RawAttribute> getRawAttributes()
-  {
-    return rawAttributes;
-  }
-
-
+  public abstract List<RawAttribute> getRawAttributes();
 
   /**
    * Adds the provided attribute to the set of raw attributes for this add
@@ -339,16 +92,7 @@
    * @param  rawAttribute  The attribute to add to the set of raw attributes for
    *                       this add operation.
    */
-  public final void addRawAttribute(RawAttribute rawAttribute)
-  {
-    rawAttributes.add(rawAttribute);
-
-    objectClasses         = null;
-    userAttributes        = null;
-    operationalAttributes = null;
-  }
-
-
+  public abstract void addRawAttribute(RawAttribute rawAttribute);
 
   /**
    * Replaces the set of raw attributes for this add operation.  This should
@@ -356,65 +100,7 @@
    *
    * @param  rawAttributes  The set of raw attributes for this add operation.
    */
-  public final void setRawAttributes(List<RawAttribute> rawAttributes)
-  {
-    this.rawAttributes = rawAttributes;
-
-    objectClasses         = null;
-    userAttributes        = null;
-    operationalAttributes = null;
-  }
-
-
-
-  /**
-   * Retrieves the set of processed objectclasses for the entry to add.  This
-   * should not be called by pre-parse plugins because this information will not
-   * yet be available.  The contents of the returned map may not be altered by
-   * the caller.
-   *
-   * @return  The set of processed objectclasses for the entry to add, or
-   *          <CODE>null</CODE> if that information is not yet available.
-   */
-  public final Map<ObjectClass,String> getObjectClasses()
-  {
-    return objectClasses;
-  }
-
-
-
-  /**
-   * Adds the provided objectclass to the entry to add.  This should only be
-   * called from pre-operation plugins.  Note that pre-operation plugin
-   * processing is invoked after access control and schema validation, so
-   * plugins should be careful to only make changes that will not violate either
-   * schema or access control rules.
-   *
-   * @param  objectClass  The objectclass to add to the entry.
-   * @param  name         The name to use for the objectclass.
-   */
-  public final void addObjectClass(ObjectClass objectClass, String name)
-  {
-    objectClasses.put(objectClass, name);
-  }
-
-
-
-  /**
-   * Removes the provided objectclass from the entry to add.  This should only
-   * be called from pre-operation plugins.  Note that pre-operation plugin
-   * processing is invoked after access control and schema validation, so
-   * plugins should be careful to only make changes that will not violate either
-   * schema or access control rules.
-   *
-   * @param  objectClass  The objectclass to remove from the entry.
-   */
-  public final void removeObjectClass(ObjectClass objectClass)
-  {
-    objectClasses.remove(objectClass);
-  }
-
-
+  public abstract void setRawAttributes(List<RawAttribute> rawAttributes);
 
   /**
    * Retrieves the set of processed user attributes for the entry to add.  This
@@ -425,28 +111,7 @@
    * @return  The set of processed user attributes for the entry to add, or
    *          <CODE>null</CODE> if that information is not yet available.
    */
-  public final Map<AttributeType,List<Attribute>> getUserAttributes()
-  {
-    return userAttributes;
-  }
-
-
-
-  /**
-   * Retrieves the set of processed operational attributes for the entry to add.
-   * This should not be called by pre-parse plugins because this information
-   * will not yet be available.  The contents of the returned map may be altered
-   * by the caller.
-   *
-   * @return  The set of processed operational attributes for the entry to add,
-   *          or <CODE>null</CODE> if that information is not yet available.
-   */
-  public final Map<AttributeType,List<Attribute>> getOperationalAttributes()
-  {
-    return operationalAttributes;
-  }
-
-
+  public abstract Map<AttributeType, List<Attribute>> getUserAttributes();
 
   /**
    * Sets the specified attribute in the entry to add, overwriting any existing
@@ -459,34 +124,8 @@
    * @param  attributeType  The attribute type for the attribute.
    * @param  attributeList  The attribute list for the provided attribute type.
    */
-  public final void setAttribute(AttributeType attributeType,
-                                 List<Attribute> attributeList)
-  {
-    if (attributeType.isOperational())
-    {
-      if ((attributeList == null) || (attributeList.isEmpty()))
-      {
-        operationalAttributes.remove(attributeType);
-      }
-      else
-      {
-        operationalAttributes.put(attributeType, attributeList);
-      }
-    }
-    else
-    {
-      if ((attributeList == null) || (attributeList.isEmpty()))
-      {
-        userAttributes.remove(attributeType);
-      }
-      else
-      {
-        userAttributes.put(attributeType, attributeList);
-      }
-    }
-  }
-
-
+  public abstract void setAttribute(AttributeType attributeType,
+      List<Attribute> attributeList);
 
   /**
    * Removes the specified attribute from the entry to add. This should only be
@@ -497,67 +136,7 @@
    *
    * @param  attributeType  The attribute tyep for the attribute to remove.
    */
-  public final void removeAttribute(AttributeType attributeType)
-  {
-    if (attributeType.isOperational())
-    {
-      operationalAttributes.remove(attributeType);
-    }
-    else
-    {
-      userAttributes.remove(attributeType);
-    }
-  }
-
-
-
-  /**
-   * Retrieves the entry to be added to the server.  Note that this will not be
-   * available to pre-parse plugins or during the conflict resolution portion of
-   * the synchronization processing.
-   *
-   * @return  The entry to be added to the server, or <CODE>null</CODE> if it is
-   *          not yet available.
-   */
-  public final Entry getEntryToAdd()
-  {
-    return entry;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
+  public abstract void removeAttribute(AttributeType attributeType);
 
   /**
    * Retrieves the change number that has been assigned to this operation.
@@ -566,12 +145,7 @@
    *          if none has been assigned yet or if there is no applicable
    *          synchronization mechanism in place that uses change numbers.
    */
-  public final long getChangeNumber()
-  {
-    return changeNumber;
-  }
-
-
+  public abstract long getChangeNumber();
 
   /**
    * Specifies the change number that has been assigned to this operation by the
@@ -580,131 +154,52 @@
    * @param  changeNumber  The change number that has been assigned to this
    *                       operation by the synchronization mechanism.
    */
-  public final void setChangeNumber(long changeNumber)
-  {
-    this.changeNumber = changeNumber;
-  }
-
-
+  public abstract void setChangeNumber(long changeNumber);
 
   /**
-   * {@inheritDoc}
+   * Retrieves the set of processed objectclasses for the entry to add.  This
+   * should not be called by pre-parse plugins because this information will not
+   * yet be available.  The contents of the returned map may not be altered by
+   * the caller.
+   *
+   * @return  The set of processed objectclasses for the entry to add, or
+   *          <CODE>null</CODE> if that information is not yet available.
    */
-  @Override()
-  public final OperationType getOperationType()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    return OperationType.ADD;
-  }
-
-
+  public abstract Map<ObjectClass,String> getObjectClasses();
 
   /**
-   * {@inheritDoc}
+   * Adds the provided objectclass to the entry to add.  This should only be
+   * called from pre-operation plugins.  Note that pre-operation plugin
+   * processing is invoked after access control and schema validation, so
+   * plugins should be careful to only make changes that will not violate either
+   * schema or access control rules.
+   *
+   * @param  objectClass  The objectclass to add to the entry.
+   * @param  name         The name to use for the objectclass.
    */
-  @Override()
-  public final void disconnectClient(DisconnectReason disconnectReason,
-                                     boolean sendNotification, String message,
-                                     int messageID)
-  {
-    // Before calling clientConnection.disconnect, we need to mark this
-    // operation as cancelled so that the attempt to cancel it later won't cause
-    // an unnecessary delay.
-    setCancelResult(CancelResult.CANCELED);
-
-    clientConnection.disconnect(disconnectReason, sendNotification, message,
-                                messageID);
-  }
-
-
+  public abstract void addObjectClass(ObjectClass objectClass, String name);
 
   /**
-   * {@inheritDoc}
+   * Removes the provided objectclass from the entry to add.  This should only
+   * be called from pre-operation plugins.  Note that pre-operation plugin
+   * processing is invoked after access control and schema validation, so
+   * plugins should be careful to only make changes that will not violate either
+   * schema or access control rules.
+   *
+   * @param  objectClass  The objectclass to remove from the entry.
    */
-  @Override()
-  public final String[][] getRequestLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) }
-    };
-  }
-
-
+  public abstract void removeObjectClass(ObjectClass objectClass);
 
   /**
-   * {@inheritDoc}
+   * Retrieves the set of processed operational attributes for the entry to add.
+   * This should not be called by pre-parse plugins because this information
+   * will not yet be available.  The contents of the returned map may be altered
+   * by the caller.
+   *
+   * @return  The set of processed operational attributes for the entry to add,
+   *          or <CODE>null</CODE> if that information is not yet available.
    */
-  @Override()
-  public final String[][] getResponseLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    String resultCode = String.valueOf(getResultCode().getIntValue());
-
-    String errorMessage;
-    StringBuilder errorMessageBuffer = getErrorMessage();
-    if (errorMessageBuffer == null)
-    {
-      errorMessage = null;
-    }
-    else
-    {
-      errorMessage = errorMessageBuffer.toString();
-    }
-
-    String matchedDNStr;
-    DN matchedDN = getMatchedDN();
-    if (matchedDN == null)
-    {
-      matchedDNStr = null;
-    }
-    else
-    {
-      matchedDNStr = matchedDN.toString();
-    }
-
-    String referrals;
-    List<String> referralURLs = getReferralURLs();
-    if ((referralURLs == null) || referralURLs.isEmpty())
-    {
-      referrals = null;
-    }
-    else
-    {
-      StringBuilder buffer = new StringBuilder();
-      Iterator<String> iterator = referralURLs.iterator();
-      buffer.append(iterator.next());
-
-      while (iterator.hasNext())
-      {
-        buffer.append(", ");
-        buffer.append(iterator.next());
-      }
-
-      referrals = buffer.toString();
-    }
-
-    String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
-      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
-      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
-      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
-      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
-    };
-  }
-
-
+  public abstract Map<AttributeType,List<Attribute>> getOperationalAttributes();
 
   /**
    * Retrieves the proxied authorization DN for this operation if proxied
@@ -714,1914 +209,17 @@
    *          authorization has been requested, or {@code null} if proxied
    *          authorization has not been requested.
    */
-  public DN getProxiedAuthorizationDN()
-  {
-    return proxiedAuthorizationDN;
-  }
-
-
+  public abstract DN getProxiedAuthorizationDN();
 
   /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final ArrayList<Control> getResponseControls()
-  {
-    return responseControls;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void addResponseControl(Control control)
-  {
-    responseControls.add(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void removeResponseControl(Control control)
-  {
-    responseControls.remove(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void run()
-  {
-    // Start the processing timer.
-    processingStartTime = System.currentTimeMillis();
-    setResultCode(ResultCode.UNDEFINED);
-
-
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
-      return;
-    }
-
-
-    // Get the plugin config manager that will be used for invoking plugins.
-    PluginConfigManager pluginConfigManager =
-         DirectoryServer.getPluginConfigManager();
-    boolean skipPostOperation = false;
-
-
-    // Create a labeled block of code that we can break out of if a problem is
-    // detected.
-addProcessing:
-    {
-      // Invoke the pre-parse add plugins.
-      PreParsePluginResult preParseResult =
-           pluginConfigManager.invokePreParseAddPlugins(this);
-      if (preParseResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the request and
-        // result and return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-
-        logAddRequest(this);
-        logAddResponse(this);
-        pluginConfigManager.invokePostResponseAddPlugins(this);
-        return;
-      }
-      else if (preParseResult.sendResponseImmediately())
-      {
-        skipPostOperation = true;
-        logAddRequest(this);
-        break addProcessing;
-      }
-      else if (preParseResult.skipCoreProcessing())
-      {
-        skipPostOperation = false;
-        break addProcessing;
-      }
-
-
-      // Log the add request message.
-      logAddRequest(this);
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logAddResponse(this);
-        pluginConfigManager.invokePostResponseAddPlugins(this);
-        return;
-      }
-
-
-      // Process the entry DN and set of attributes to convert them from their
-      // raw forms as provided by the client to the forms required for the rest
-      // of the add processing.
-      try
-      {
-        if (entryDN == null)
-        {
-          entryDN = DN.decode(rawEntryDN);
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        setMatchedDN(de.getMatchedDN());
-        setReferralURLs(de.getReferralURLs());
-
-        break addProcessing;
-      }
-
-
-      if ((objectClasses == null) || (userAttributes == null) ||
-          (operationalAttributes == null))
-      {
-        objectClasses         = new HashMap<ObjectClass,String>();
-        userAttributes        = new HashMap<AttributeType,List<Attribute>>();
-        operationalAttributes = new HashMap<AttributeType,List<Attribute>>();
-        for (RawAttribute a : rawAttributes)
-        {
-          try
-          {
-            Attribute attr = a.toAttribute();
-            AttributeType attrType = attr.getAttributeType();
-
-
-            // If the attribute type is marked "NO-USER-MODIFICATION" then fail
-            // unless this is an internal operation or is related to
-            // synchronization in some way.
-            if (attrType.isNoUserModification())
-            {
-              if (! (isInternalOperation() || isSynchronizationOperation()))
-              {
-                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                appendErrorMessage(getMessage(MSGID_ADD_ATTR_IS_NO_USER_MOD,
-                                              String.valueOf(entryDN),
-                                              attr.getName()));
-                break addProcessing;
-              }
-            }
-
-
-            if (attrType.isObjectClassType())
-            {
-              for (ByteString os : a.getValues())
-              {
-                String ocName = os.toString();
-                ObjectClass oc =
-                     DirectoryServer.getObjectClass(toLowerCase(ocName));
-                if (oc == null)
-                {
-                  oc = DirectoryServer.getDefaultObjectClass(ocName);
-                }
-
-                objectClasses.put(oc,ocName);
-              }
-            }
-            else if (attrType.isOperational())
-            {
-              List<Attribute> attrs = operationalAttributes.get(attrType);
-              if (attrs == null)
-              {
-                attrs = new ArrayList<Attribute>(1);
-                attrs.add(attr);
-                operationalAttributes.put(attrType, attrs);
-              }
-              else
-              {
-                attrs.add(attr);
-              }
-            }
-            else
-            {
-              List<Attribute> attrs = userAttributes.get(attrType);
-              if (attrs == null)
-              {
-                attrs = new ArrayList<Attribute>(1);
-                attrs.add(attr);
-                userAttributes.put(attrType, attrs);
-              }
-              else
-              {
-                // Check to see if any of the existing attributes in the list
-                // have the same set of options.  If so, then add the values
-                // to that attribute.
-                boolean attributeSeen = false;
-                for (Attribute ea : attrs)
-                {
-                  if (ea.optionsEqual(attr.getOptions()))
-                  {
-                    LinkedHashSet<AttributeValue> valueSet = ea.getValues();
-                    valueSet.addAll(attr.getValues());
-                    attributeSeen = true;
-                  }
-                }
-                if (!attributeSeen)
-                {
-                  // This is the first occurrence of the attribute and options.
-                  attrs.add(attr);
-                }
-              }
-            }
-          }
-          catch (LDAPException le)
-          {
-            setResultCode(ResultCode.valueOf(le.getResultCode()));
-            appendErrorMessage(le.getMessage());
-
-            break addProcessing;
-          }
-        }
-      }
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logAddResponse(this);
-        pluginConfigManager.invokePostResponseAddPlugins(this);
-        return;
-      }
-
-
-      // Grab a read lock on the parent entry, if there is one.  We need to do
-      // this to ensure that the parent is not deleted or renamed while this add
-      // is in progress, and we could also need it to check the entry against
-      // a DIT structure rule.
-      Lock parentLock = null;
-      Lock entryLock  = null;
-
-      DN parentDN = entryDN.getParentDNInSuffix();
-      if (parentDN == null)
-      {
-        // Either this entry is a suffix or doesn't belong in the directory.
-        if (DirectoryServer.isNamingContext(entryDN))
-        {
-          // This is fine.  This entry is one of the configured suffixes.
-          parentLock = null;
-        }
-        else if (entryDN.isNullDN())
-        {
-          // This is not fine.  The root DSE cannot be added.
-          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-          appendErrorMessage(getMessage(MSGID_ADD_CANNOT_ADD_ROOT_DSE));
-          break addProcessing;
-        }
-        else
-        {
-          // The entry doesn't have a parent but isn't a suffix.  This is not
-          // allowed.
-          setResultCode(ResultCode.NO_SUCH_OBJECT);
-          appendErrorMessage(getMessage(MSGID_ADD_ENTRY_NOT_SUFFIX,
-                                        String.valueOf(entryDN)));
-          break addProcessing;
-        }
-      }
-      else
-      {
-        for (int i=0; i < 3; i++)
-        {
-          parentLock = LockManager.lockRead(parentDN);
-          if (parentLock != null)
-          {
-            break;
-          }
-        }
-
-        if (parentLock == null)
-        {
-          setResultCode(DirectoryServer.getServerErrorResultCode());
-          appendErrorMessage(getMessage(MSGID_ADD_CANNOT_LOCK_PARENT,
-                                        String.valueOf(entryDN),
-                                        String.valueOf(parentDN)));
-
-          skipPostOperation = true;
-          break addProcessing;
-        }
-      }
-
-
-      try
-      {
-        // Check for and handle a request to cancel this operation.
-        if (cancelRequest != null)
-        {
-          indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
-          logAddResponse(this);
-          pluginConfigManager.invokePostResponseAddPlugins(this);
-          return;
-        }
-
-
-        // Grab a write lock on the target entry.  We'll need to do this
-        // eventually anyway, and we want to make sure that the two locks are
-        // always released when exiting this method, no matter what.  Since
-        // the entry shouldn't exist yet, locking earlier than necessary
-        // shouldn't cause a problem.
-        for (int i=0; i < 3; i++)
-        {
-          entryLock = LockManager.lockWrite(entryDN);
-          if (entryLock != null)
-          {
-            break;
-          }
-        }
-
-        if (entryLock == null)
-        {
-          setResultCode(DirectoryServer.getServerErrorResultCode());
-          appendErrorMessage(getMessage(MSGID_ADD_CANNOT_LOCK_ENTRY,
-                                        String.valueOf(entryDN)));
-
-          skipPostOperation = true;
-          break addProcessing;
-        }
-
-
-        // Invoke any conflict resolution processing that might be needed by the
-        // synchronization provider.
-        for (SynchronizationProvider provider :
-             DirectoryServer.getSynchronizationProviders())
-        {
-          try
-          {
-            SynchronizationProviderResult result =
-                 provider.handleConflictResolution(this);
-            if (! result.continueOperationProcessing())
-            {
-              break addProcessing;
-            }
-          }
-          catch (DirectoryException de)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, de);
-            }
-
-            logError(ErrorLogCategory.SYNCHRONIZATION,
-                     ErrorLogSeverity.SEVERE_ERROR,
-                     MSGID_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED,
-                     getConnectionID(), getOperationID(),
-                     getExceptionMessage(de));
-
-            setResponseData(de);
-            break addProcessing;
-          }
-        }
-
-
-        // Check to see if the entry already exists.  We do this before
-        // checking whether the parent exists to ensure a referral entry
-        // above the parent results in a correct referral.
-        try
-        {
-          if (DirectoryServer.entryExists(entryDN))
-          {
-            setResultCode(ResultCode.ENTRY_ALREADY_EXISTS);
-            appendErrorMessage(getMessage(MSGID_ADD_ENTRY_ALREADY_EXISTS,
-                                          String.valueOf(entryDN)));
-            break addProcessing;
-          }
-        }
-        catch (DirectoryException de)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
-          }
-
-          setResultCode(de.getResultCode());
-          appendErrorMessage(de.getErrorMessage());
-          setMatchedDN(de.getMatchedDN());
-          setReferralURLs(de.getReferralURLs());
-          break addProcessing;
-        }
-
-
-        // Get the parent entry, if it exists.
-        Entry parentEntry = null;
-        if (parentDN != null)
-        {
-          try
-          {
-            parentEntry = DirectoryServer.getEntry(parentDN);
-
-            if (parentEntry == null)
-            {
-              DN matchedDN = parentDN.getParentDNInSuffix();
-              while (matchedDN != null)
-              {
-                try
-                {
-                  if (DirectoryServer.entryExists(matchedDN))
-                  {
-                    setMatchedDN(matchedDN);
-                    break;
-                  }
-                }
-                catch (Exception e)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                  }
-                  break;
-                }
-
-                matchedDN = matchedDN.getParentDNInSuffix();
-              }
-
-
-              // The parent doesn't exist, so this add can't be successful.
-              setResultCode(ResultCode.NO_SUCH_OBJECT);
-              appendErrorMessage(getMessage(MSGID_ADD_NO_PARENT,
-                                            String.valueOf(entryDN),
-                                            String.valueOf(parentDN)));
-              break addProcessing;
-            }
-          }
-          catch (DirectoryException de)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, de);
-            }
-
-            setResultCode(de.getResultCode());
-            appendErrorMessage(de.getErrorMessage());
-            setMatchedDN(de.getMatchedDN());
-            setReferralURLs(de.getReferralURLs());
-            break addProcessing;
-          }
-        }
-
-
-        // Check to make sure that all of the RDN attributes are included as
-        // attribute values.  If not, then either add them or report an error.
-        RDN rdn = entryDN.getRDN();
-        int numAVAs = rdn.getNumValues();
-        for (int i=0; i < numAVAs; i++)
-        {
-          AttributeType  t = rdn.getAttributeType(i);
-          AttributeValue v = rdn.getAttributeValue(i);
-          String         n = rdn.getAttributeName(i);
-          if (t.isOperational())
-          {
-            List<Attribute> attrList = operationalAttributes.get(t);
-            if (attrList == null)
-            {
-              if (isSynchronizationOperation() ||
-                  DirectoryServer.addMissingRDNAttributes())
-              {
-                LinkedHashSet<AttributeValue> valueList =
-                     new LinkedHashSet<AttributeValue>(1);
-                valueList.add(v);
-
-                attrList = new ArrayList<Attribute>();
-                attrList.add(new Attribute(t, n, valueList));
-
-                operationalAttributes.put(t, attrList);
-              }
-              else
-              {
-                setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-
-                int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              n));
-
-                break addProcessing;
-              }
-            }
-            else
-            {
-              boolean found = false;
-              for (Attribute a : attrList)
-              {
-                if (a.hasOptions())
-                {
-                  continue;
-                }
-                else
-                {
-                  if (! a.hasValue(v))
-                  {
-                    a.getValues().add(v);
-                  }
-
-                  found = true;
-                  break;
-                }
-              }
-
-              if (! found)
-              {
-                if (isSynchronizationOperation() ||
-                    DirectoryServer.addMissingRDNAttributes())
-                {
-                  LinkedHashSet<AttributeValue> valueList =
-                       new LinkedHashSet<AttributeValue>(1);
-                  valueList.add(v);
-                  attrList.add(new Attribute(t, n, valueList));
-                }
-                else
-                {
-                  setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-
-                  int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
-                  appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                                n));
-
-                  break addProcessing;
-                }
-              }
-            }
-          }
-          else
-          {
-            List<Attribute> attrList = userAttributes.get(t);
-            if (attrList == null)
-            {
-              if (isSynchronizationOperation() ||
-                  DirectoryServer.addMissingRDNAttributes())
-              {
-                LinkedHashSet<AttributeValue> valueList =
-                     new LinkedHashSet<AttributeValue>(1);
-                valueList.add(v);
-
-                attrList = new ArrayList<Attribute>();
-                attrList.add(new Attribute(t, n, valueList));
-
-                userAttributes.put(t, attrList);
-              }
-              else
-              {
-                setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-
-                int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              n));
-
-                break addProcessing;
-              }
-            }
-            else
-            {
-              boolean found = false;
-              for (Attribute a : attrList)
-              {
-                if (a.hasOptions())
-                {
-                  continue;
-                }
-                else
-                {
-                  if (! a.hasValue(v))
-                  {
-                    a.getValues().add(v);
-                  }
-
-                  found = true;
-                  break;
-                }
-              }
-
-              if (! found)
-              {
-                if (isSynchronizationOperation() ||
-                    DirectoryServer.addMissingRDNAttributes())
-                {
-                  LinkedHashSet<AttributeValue> valueList =
-                       new LinkedHashSet<AttributeValue>(1);
-                  valueList.add(v);
-                  attrList.add(new Attribute(t, n, valueList));
-                }
-                else
-                {
-                  setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-
-                  int msgID = MSGID_ADD_MISSING_RDN_ATTRIBUTE;
-                  appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                                n));
-
-                  break addProcessing;
-                }
-              }
-            }
-          }
-        }
-
-
-        // Check to make sure that all objectclasses have their superior classes
-        // listed in the entry.  If not, then add them.
-        HashSet<ObjectClass> additionalClasses = null;
-        for (ObjectClass oc : objectClasses.keySet())
-        {
-          ObjectClass superiorClass = oc.getSuperiorClass();
-          if ((superiorClass != null) &&
-              (! objectClasses.containsKey(superiorClass)))
-          {
-            if (additionalClasses == null)
-            {
-              additionalClasses = new HashSet<ObjectClass>();
-            }
-
-            additionalClasses.add(superiorClass);
-          }
-        }
-
-        if (additionalClasses != null)
-        {
-          for (ObjectClass oc : additionalClasses)
-          {
-            addObjectClassChain(oc);
-          }
-        }
-
-
-        // Create an entry object to encapsulate the set of attributes and
-        // objectclasses.
-        entry = new Entry(entryDN, objectClasses, userAttributes,
-                          operationalAttributes);
-
-
-        // Check to see if the entry includes a privilege specification.  If so,
-        // then the requester must have the PRIVILEGE_CHANGE privilege.
-        AttributeType privType =
-             DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME, true);
-        if (entry.hasAttribute(privType) &&
-            (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this)))
-        {
-          int msgID = MSGID_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES;
-          appendErrorMessage(getMessage(msgID));
-          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-          break addProcessing;
-        }
-
-        // If it's not a synchronization operation, then check
-        // to see if the entry contains one or more passwords and if they
-        // are valid in accordance with the password policies associated with
-        // the user.  Also perform any encoding that might be required by
-        // password storage schemes.
-        if (! isSynchronizationOperation())
-        {
-          // FIXME -- We need to check to see if the password policy subentry
-          //          might be specified virtually rather than as a real
-          //          attribute.
-          PasswordPolicy pwPolicy = null;
-          List<Attribute> pwAttrList =
-               entry.getAttribute(OP_ATTR_PWPOLICY_POLICY_DN);
-          if ((pwAttrList != null) && (! pwAttrList.isEmpty()))
-          {
-            Attribute a = pwAttrList.get(0);
-            LinkedHashSet<AttributeValue> valueSet = a.getValues();
-            Iterator<AttributeValue> iterator = valueSet.iterator();
-            if (iterator.hasNext())
-            {
-              DN policyDN;
-              try
-              {
-                policyDN = DN.decode(iterator.next().getValue());
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                int msgID = MSGID_ADD_INVALID_PWPOLICY_DN_SYNTAX;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              de.getErrorMessage()));
-
-                setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-                break addProcessing;
-              }
-
-              pwPolicy = DirectoryServer.getPasswordPolicy(policyDN);
-              if (pwPolicy == null)
-              {
-                int msgID = MSGID_ADD_NO_SUCH_PWPOLICY;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              String.valueOf(policyDN)));
-
-                setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-                break addProcessing;
-              }
-            }
-          }
-
-          if (pwPolicy == null)
-          {
-            pwPolicy = DirectoryServer.getDefaultPasswordPolicy();
-          }
-
-          try
-          {
-            handlePasswordPolicy(pwPolicy, entry);
-          }
-          catch (DirectoryException de)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, de);
-            }
-
-            setResponseData(de);
-            break addProcessing;
-          }
-        }
-
-
-        // Check to see if the entry is valid according to the server schema,
-        // and also whether its attributes are valid according to their syntax.
-        if (DirectoryServer.checkSchema())
-        {
-          StringBuilder invalidReason = new StringBuilder();
-          if (! entry.conformsToSchema(parentEntry, true, true, true,
-                                       invalidReason))
-          {
-            setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
-            setErrorMessage(invalidReason);
-            break addProcessing;
-          }
-          else
-          {
-            switch (DirectoryServer.getSyntaxEnforcementPolicy())
-            {
-              case REJECT:
-                invalidReason = new StringBuilder();
-                for (List<Attribute> attrList : userAttributes.values())
-                {
-                  for (Attribute a : attrList)
-                  {
-                    AttributeSyntax syntax = a.getAttributeType().getSyntax();
-                    if (syntax != null)
-                    {
-                      for (AttributeValue v : a.getValues())
-                      {
-                        if (! syntax.valueIsAcceptable(v.getValue(),
-                                                       invalidReason))
-                        {
-                          String message =
-                               getMessage(MSGID_ADD_OP_INVALID_SYNTAX,
-                                          String.valueOf(entryDN),
-                                          String.valueOf(v.getStringValue()),
-                                          String.valueOf(a.getName()),
-                                          String.valueOf(invalidReason));
-                          invalidReason = new StringBuilder(message);
-
-                          setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-                          setErrorMessage(invalidReason);
-                          break addProcessing;
-                        }
-                      }
-                    }
-                  }
-                }
-
-                for (List<Attribute> attrList :
-                     operationalAttributes.values())
-                {
-                  for (Attribute a : attrList)
-                  {
-                    AttributeSyntax syntax = a.getAttributeType().getSyntax();
-                    if (syntax != null)
-                    {
-                      for (AttributeValue v : a.getValues())
-                      {
-                        if (! syntax.valueIsAcceptable(v.getValue(),
-                                                       invalidReason))
-                        {
-                          String message =
-                               getMessage(MSGID_ADD_OP_INVALID_SYNTAX,
-                                          String.valueOf(entryDN),
-                                          String.valueOf(v.getStringValue()),
-                                          String.valueOf(a.getName()),
-                                          String.valueOf(invalidReason));
-                          invalidReason = new StringBuilder(message);
-
-                          setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-                          setErrorMessage(invalidReason);
-                          break addProcessing;
-                        }
-                      }
-                    }
-                  }
-                }
-
-                break;
-
-
-              case WARN:
-                invalidReason = new StringBuilder();
-                for (List<Attribute> attrList : userAttributes.values())
-                {
-                  for (Attribute a : attrList)
-                  {
-                    AttributeSyntax syntax = a.getAttributeType().getSyntax();
-                    if (syntax != null)
-                    {
-                      for (AttributeValue v : a.getValues())
-                      {
-                        if (! syntax.valueIsAcceptable(v.getValue(),
-                                                       invalidReason))
-                        {
-                          logError(ErrorLogCategory.SCHEMA,
-                                   ErrorLogSeverity.SEVERE_WARNING,
-                                   MSGID_ADD_OP_INVALID_SYNTAX,
-                                   String.valueOf(entryDN),
-                                   String.valueOf(v.getStringValue()),
-                                   String.valueOf(a.getName()),
-                                   String.valueOf(invalidReason));
-                        }
-                      }
-                    }
-                  }
-                }
-
-                for (List<Attribute> attrList : operationalAttributes.values())
-                {
-                  for (Attribute a : attrList)
-                  {
-                    AttributeSyntax syntax = a.getAttributeType().getSyntax();
-                    if (syntax != null)
-                    {
-                      for (AttributeValue v : a.getValues())
-                      {
-                        if (! syntax.valueIsAcceptable(v.getValue(),
-                                                       invalidReason))
-                        {
-                          logError(ErrorLogCategory.SCHEMA,
-                                   ErrorLogSeverity.SEVERE_WARNING,
-                                   MSGID_ADD_OP_INVALID_SYNTAX,
-                                   String.valueOf(entryDN),
-                                   String.valueOf(v.getStringValue()),
-                                   String.valueOf(a.getName()),
-                                   String.valueOf(invalidReason));
-                        }
-                      }
-                    }
-                  }
-                }
-
-                break;
-            }
-          }
-
-
-          // See if the entry contains any attributes or object classes marked
-          // OBSOLETE.  If so, then reject the entry.
-          for (AttributeType at : userAttributes.keySet())
-          {
-            if (at.isObsolete())
-            {
-              int    msgID   = MSGID_ADD_ATTR_IS_OBSOLETE;
-              String message = getMessage(msgID, String.valueOf(entryDN),
-                                          at.getNameOrOID());
-              appendErrorMessage(message);
-              setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-              break addProcessing;
-            }
-          }
-
-          for (AttributeType at : operationalAttributes.keySet())
-          {
-            if (at.isObsolete())
-            {
-              int    msgID   = MSGID_ADD_ATTR_IS_OBSOLETE;
-              String message = getMessage(msgID, String.valueOf(entryDN),
-                                          at.getNameOrOID());
-              appendErrorMessage(message);
-              setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-              break addProcessing;
-            }
-          }
-
-          for (ObjectClass oc : objectClasses.keySet())
-          {
-            if (oc.isObsolete())
-            {
-              int    msgID   = MSGID_ADD_OC_IS_OBSOLETE;
-              String message = getMessage(msgID, String.valueOf(entryDN),
-                                          oc.getNameOrOID());
-              appendErrorMessage(message);
-              setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-              break addProcessing;
-            }
-          }
-        }
-
-        // Check to see if there are any controls in the request. If so,
-        // then
-        // see if there is any special processing required.
-        boolean                    noOp            = false;
-        LDAPPostReadRequestControl postReadRequest = null;
-        List<Control> requestControls = getRequestControls();
-        if ((requestControls != null) && (! requestControls.isEmpty()))
-        {
-          for (int i=0; i < requestControls.size(); i++)
-          {
-            Control c   = requestControls.get(i);
-            String  oid = c.getOID();
-
-            if (oid.equals(OID_LDAP_ASSERTION))
-            {
-              LDAPAssertionRequestControl assertControl;
-              if (c instanceof LDAPAssertionRequestControl)
-              {
-                assertControl = (LDAPAssertionRequestControl) c;
-              }
-              else
-              {
-                try
-                {
-                  assertControl = LDAPAssertionRequestControl.decodeControl(c);
-                  requestControls.set(i, assertControl);
-                }
-                catch (LDAPException le)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break addProcessing;
-                }
-              }
-
-              try
-              {
-                // FIXME -- We need to determine whether the current user has
-                //          permission to make this determination.
-                SearchFilter filter = assertControl.getSearchFilter();
-                if (! filter.matchesEntry(entry))
-                {
-                  setResultCode(ResultCode.ASSERTION_FAILED);
-
-                  appendErrorMessage(getMessage(MSGID_ADD_ASSERTION_FAILED,
-                                                String.valueOf(entryDN)));
-
-                  break addProcessing;
-                }
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(ResultCode.PROTOCOL_ERROR);
-
-                int msgID = MSGID_ADD_CANNOT_PROCESS_ASSERTION_FILTER;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              de.getErrorMessage()));
-
-                break addProcessing;
-              }
-            }
-            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
-            {
-              noOp = true;
-            }
-            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);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break addProcessing;
-                }
-              }
-            }
-            else if (oid.equals(OID_PROXIED_AUTH_V1))
-            {
-              // The requester must have the PROXIED_AUTH privilige in order to
-              // be able to use this control.
-              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-              {
-                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-                appendErrorMessage(getMessage(msgID));
-                setResultCode(ResultCode.AUTHORIZATION_DENIED);
-                break addProcessing;
-              }
-
-
-              ProxiedAuthV1Control proxyControl;
-              if (c instanceof ProxiedAuthV1Control)
-              {
-                proxyControl = (ProxiedAuthV1Control) c;
-              }
-              else
-              {
-                try
-                {
-                  proxyControl = ProxiedAuthV1Control.decodeControl(c);
-                }
-                catch (LDAPException le)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break addProcessing;
-                }
-              }
-
-
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
-
-                break addProcessing;
-              }
-
-              if (AccessControlConfigManager.getInstance()
-                      .getAccessControlHandler().isProxiedAuthAllowed(this,
-                      authorizationEntry) == false) {
-                setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-                int msgID = MSGID_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-                skipPostOperation = true;
-                break addProcessing;
-              }
-              setAuthorizationEntry(authorizationEntry);
-              if (authorizationEntry == null)
-              {
-                proxiedAuthorizationDN = DN.nullDN();
-              }
-              else
-              {
-                proxiedAuthorizationDN = authorizationEntry.getDN();
-              }
-            }
-            else if (oid.equals(OID_PROXIED_AUTH_V2))
-            {
-              // The requester must have the PROXIED_AUTH privilige in order to
-              // be able to use this control.
-              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-              {
-                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-                appendErrorMessage(getMessage(msgID));
-                setResultCode(ResultCode.AUTHORIZATION_DENIED);
-                break addProcessing;
-              }
-
-
-              ProxiedAuthV2Control proxyControl;
-              if (c instanceof ProxiedAuthV2Control)
-              {
-                proxyControl = (ProxiedAuthV2Control) c;
-              }
-              else
-              {
-                try
-                {
-                  proxyControl = ProxiedAuthV2Control.decodeControl(c);
-                }
-                catch (LDAPException le)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break addProcessing;
-                }
-              }
-
-
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
-
-                break addProcessing;
-              }
-
-              if (AccessControlConfigManager.getInstance()
-                      .getAccessControlHandler().isProxiedAuthAllowed(this,
-                      authorizationEntry) == false) {
-                setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-                int msgID = MSGID_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-                skipPostOperation = true;
-                break addProcessing;
-              }
-              setAuthorizationEntry(authorizationEntry);
-              if (authorizationEntry == null)
-              {
-                proxiedAuthorizationDN = DN.nullDN();
-              }
-              else
-              {
-                proxiedAuthorizationDN = authorizationEntry.getDN();
-              }
-            }
-
-            // NYI -- Add support for additional controls.
-            else if (c.isCritical())
-            {
-              Backend backend = DirectoryServer.getBackend(entryDN);
-              if ((backend == null) || (! backend.supportsControl(oid)))
-              {
-                setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
-
-                int msgID = MSGID_ADD_UNSUPPORTED_CRITICAL_CONTROL;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              oid));
-
-                break addProcessing;
-              }
-            }
-          }
-        }
-
-
-        // Check to see if the client has permission to perform the add.
-
-        // FIXME: for now assume that this will check all permission
-        // pertinent to the operation. This includes proxy authorization
-        // and any other controls specified.
-
-        // FIXME: earlier checks to see if the entry already exists or
-        // if the parent entry does not exist may have already exposed
-        // sensitive information to the client.
-        if (AccessControlConfigManager.getInstance()
-            .getAccessControlHandler().isAllowed(this) == false) {
-          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-          int msgID = MSGID_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-          appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-          skipPostOperation = true;
-          break addProcessing;
-        }
-
-        // Check for and handle a request to cancel this operation.
-        if (cancelRequest != null)
-        {
-          indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
-          logAddResponse(this);
-          pluginConfigManager.invokePostResponseAddPlugins(this);
-          return;
-        }
-
-
-        // If the operation is not a synchronization operation,
-        // Invoke the pre-operation modify plugins.
-        if (!isSynchronizationOperation())
-        {
-          PreOperationPluginResult preOpResult =
-            pluginConfigManager.invokePreOperationAddPlugins(this);
-          if (preOpResult.connectionTerminated())
-          {
-            // There's no point in continuing with anything.  Log the result
-            // and return.
-            setResultCode(ResultCode.CANCELED);
-
-            int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-            appendErrorMessage(getMessage(msgID));
-
-            processingStopTime = System.currentTimeMillis();
-
-            logAddResponse(this);
-            pluginConfigManager.invokePostResponseAddPlugins(this);
-            return;
-          }
-          else if (preOpResult.sendResponseImmediately())
-          {
-            skipPostOperation = true;
-            break addProcessing;
-          }
-          else if (preOpResult.skipCoreProcessing())
-          {
-            skipPostOperation = false;
-            break addProcessing;
-          }
-        }
-
-
-        // Check for and handle a request to cancel this operation.
-        if (cancelRequest != null)
-        {
-          indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
-          logAddResponse(this);
-          pluginConfigManager.invokePostResponseAddPlugins(this);
-          return;
-        }
-
-
-        // Actually perform the add operation.  This should also include taking
-        // care of any synchronization that might be needed.
-        Backend backend = DirectoryServer.getBackend(entryDN);
-        if (backend == null)
-        {
-          setResultCode(ResultCode.NO_SUCH_OBJECT);
-          appendErrorMessage("No backend for entry " + entryDN.toString());
-        }
-        else
-        {
-          // If it is not a private backend, then check to see if the server or
-          // backend is operating in read-only mode.
-          if (! backend.isPrivateBackend())
-          {
-            switch (DirectoryServer.getWritabilityMode())
-            {
-              case DISABLED:
-                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                appendErrorMessage(getMessage(MSGID_ADD_SERVER_READONLY,
-                                              String.valueOf(entryDN)));
-                break addProcessing;
-
-              case INTERNAL_ONLY:
-                if (! (isInternalOperation() || isSynchronizationOperation()))
-                {
-                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                  appendErrorMessage(getMessage(MSGID_ADD_SERVER_READONLY,
-                                                String.valueOf(entryDN)));
-                  break addProcessing;
-                }
-            }
-
-            switch (backend.getWritabilityMode())
-            {
-              case DISABLED:
-                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                appendErrorMessage(getMessage(MSGID_ADD_BACKEND_READONLY,
-                                              String.valueOf(entryDN)));
-                break addProcessing;
-
-              case INTERNAL_ONLY:
-                if (! (isInternalOperation() || isSynchronizationOperation()))
-                {
-                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                  appendErrorMessage(getMessage(MSGID_ADD_BACKEND_READONLY,
-                                                String.valueOf(entryDN)));
-                  break addProcessing;
-                }
-            }
-          }
-
-
-          try
-          {
-            if (noOp)
-            {
-              appendErrorMessage(getMessage(MSGID_ADD_NOOP));
-
-              // FIXME -- We must set a result code other than SUCCESS.
-            }
-            else
-            {
-              for (SynchronizationProvider provider :
-                   DirectoryServer.getSynchronizationProviders())
-              {
-                try
-                {
-                  SynchronizationProviderResult result =
-                       provider.doPreOperation(this);
-                  if (! result.continueOperationProcessing())
-                  {
-                    break addProcessing;
-                  }
-                }
-                catch (DirectoryException de)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                  }
-
-                  logError(ErrorLogCategory.SYNCHRONIZATION,
-                           ErrorLogSeverity.SEVERE_ERROR,
-                           MSGID_ADD_SYNCH_PREOP_FAILED, getConnectionID(),
-                           getOperationID(), getExceptionMessage(de));
-
-                  setResponseData(de);
-                  break addProcessing;
-                }
-              }
-
-              backend.addEntry(entry, this);
-            }
-
-            if (postReadRequest != null)
-            {
-              Entry addedEntry = entry.duplicate(true);
-
-              if (! postReadRequest.allowsAttribute(
-                         DirectoryServer.getObjectClassAttributeType()))
-              {
-                addedEntry.removeAttribute(
-                     DirectoryServer.getObjectClassAttributeType());
-              }
-
-              if (! postReadRequest.returnAllUserAttributes())
-              {
-                Iterator<AttributeType> iterator =
-                     addedEntry.getUserAttributes().keySet().iterator();
-                while (iterator.hasNext())
-                {
-                  AttributeType attrType = iterator.next();
-                  if (! postReadRequest.allowsAttribute(attrType))
-                  {
-                    iterator.remove();
-                  }
-                }
-              }
-
-              if (! postReadRequest.returnAllOperationalAttributes())
-              {
-                Iterator<AttributeType> iterator =
-                     addedEntry.getOperationalAttributes().keySet().iterator();
-                while (iterator.hasNext())
-                {
-                  AttributeType attrType = iterator.next();
-                  if (! postReadRequest.allowsAttribute(attrType))
-                  {
-                    iterator.remove();
-                  }
-                }
-              }
-
-              // FIXME -- Check access controls on the entry to see if it should
-              //          be returned or if any attributes need to be stripped
-              //          out..
-              SearchResultEntry searchEntry = new SearchResultEntry(addedEntry);
-              LDAPPostReadResponseControl responseControl =
-                   new LDAPPostReadResponseControl(postReadRequest.getOID(),
-                                                   postReadRequest.isCritical(),
-                                                   searchEntry);
-
-              responseControls.add(responseControl);
-            }
-
-            setResultCode(ResultCode.SUCCESS);
-          }
-          catch (DirectoryException de)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, de);
-            }
-
-            setResultCode(de.getResultCode());
-            appendErrorMessage(de.getErrorMessage());
-            setMatchedDN(de.getMatchedDN());
-            setReferralURLs(de.getReferralURLs());
-
-            break addProcessing;
-          }
-          catch (CancelledOperationException coe)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, coe);
-            }
-
-            CancelResult cancelResult = coe.getCancelResult();
-
-            setCancelResult(cancelResult);
-            setResultCode(cancelResult.getResultCode());
-
-            String message = coe.getMessage();
-            if ((message != null) && (message.length() > 0))
-            {
-              appendErrorMessage(message);
-            }
-
-            break addProcessing;
-          }
-        }
-      }
-      finally
-      {
-        if (entryLock != null)
-        {
-          LockManager.unlock(entryDN, entryLock);
-        }
-
-        if (parentLock != null)
-        {
-          LockManager.unlock(parentDN, parentLock);
-        }
-
-
-        for (SynchronizationProvider provider :
-             DirectoryServer.getSynchronizationProviders())
-        {
-          try
-          {
-            provider.doPostOperation(this);
-          }
-          catch (DirectoryException de)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, de);
-            }
-
-            logError(ErrorLogCategory.SYNCHRONIZATION,
-                     ErrorLogSeverity.SEVERE_ERROR,
-                     MSGID_ADD_SYNCH_POSTOP_FAILED, 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 add plugins.
-    if (! skipPostOperation)
-    {
-      // FIXME -- Should this also be done while holding the locks?
-      PostOperationPluginResult postOpResult =
-           pluginConfigManager.invokePostOperationAddPlugins(this);
-      if (postOpResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the result and
-        // return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-
-        logAddResponse(this);
-        pluginConfigManager.invokePostResponseAddPlugins(this);
-        return;
-      }
-    }
-
-
-    // Notify any change notification listeners that might be registered with
-    // the server.
-    if ((getResultCode() == ResultCode.SUCCESS) && (entry != null))
-    {
-      for (ChangeNotificationListener changeListener :
-           DirectoryServer.getChangeNotificationListeners())
-      {
-        try
-        {
-          changeListener.handleAddOperation(this, entry);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ADD_ERROR_NOTIFYING_CHANGE_LISTENER;
-          String message = getMessage(msgID, getExceptionMessage(e));
-          logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
-                   message, msgID);
-        }
-      }
-    }
-
-
-    // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
-
-
-    // Send the add response to the client.
-    getClientConnection().sendResponse(this);
-
-
-    // Log the add response.
-    logAddResponse(this);
-
-
-    // Notify any persistent searches that might be registered with the server.
-    if ((getResultCode() == ResultCode.SUCCESS) && (entry != null))
-    {
-      for (PersistentSearch persistentSearch :
-           DirectoryServer.getPersistentSearches())
-      {
-        try
-        {
-          persistentSearch.processAdd(this, entry);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ADD_ERROR_NOTIFYING_PERSISTENT_SEARCH;
-          String message = getMessage(msgID, String.valueOf(persistentSearch),
-                                      getExceptionMessage(e));
-          logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
-                   message, msgID);
-
-          DirectoryServer.deregisterPersistentSearch(persistentSearch);
-        }
-      }
-    }
-
-
-    // Invoke the post-response add plugins.
-    pluginConfigManager.invokePostResponseAddPlugins(this);
-  }
-
-
-
-  /**
-   * Adds the provided objectClass to the entry, along with its superior classes
-   * if appropriate.
+   * Set the proxied authorization DN for this operation if proxied
+   * authorization has been requested.
    *
-   * @param  objectClass  The objectclass to add to the entry.
+   * @param proxiedAuthorizationDN
+   *          The proxied authorization DN for this operation if proxied
+   *          authorization has been requested, or {@code null} if proxied
+   *          authorization has not been requested.
    */
-  private final void addObjectClassChain(ObjectClass objectClass)
-  {
-    if (! objectClasses.containsKey(objectClass))
-    {
-      objectClasses.put(objectClass, objectClass.getNameOrOID());
-    }
+  public abstract void setProxiedAuthorizationDN(DN proxiedAuthorizationDN);
 
-    ObjectClass superiorClass = objectClass.getSuperiorClass();
-    if ((superiorClass != null) &&
-        (! objectClasses.containsKey(superiorClass)))
-    {
-      addObjectClassChain(superiorClass);
-    }
-  }
-
-
-
-  /**
-   * Performs all password policy processing necessary for the provided add
-   * operation.
-   *
-   * @param  passwordPolicy  The password policy associated with the entry to be
-   *                         added.
-   * @param  userEntry       The user entry being added.
-   *
-   * @throws  DirectoryException  If a problem occurs while performing password
-   *                              policy processing for the add operation.
-   */
-  private final void handlePasswordPolicy(PasswordPolicy passwordPolicy,
-                                          Entry userEntry)
-         throws DirectoryException
-  {
-    // See if a password was specified.
-    AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
-    List<Attribute> attrList = userEntry.getAttribute(passwordAttribute);
-    if ((attrList == null) || attrList.isEmpty())
-    {
-      // The entry doesn't have a password, so no action is required.
-      return;
-    }
-    else if (attrList.size() > 1)
-    {
-      // This must mean there are attribute options, which we won't allow for
-      // passwords.
-      int msgID = MSGID_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED;
-      String message = getMessage(msgID, passwordAttribute.getNameOrOID());
-      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
-                                   msgID);
-    }
-
-    Attribute passwordAttr = attrList.get(0);
-    if (passwordAttr.hasOptions())
-    {
-      int msgID = MSGID_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED;
-      String message = getMessage(msgID, passwordAttribute.getNameOrOID());
-      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
-                                   msgID);
-    }
-
-    LinkedHashSet<AttributeValue> values = passwordAttr.getValues();
-    if (values.isEmpty())
-    {
-      // This will be treated the same as not having a password.
-      return;
-    }
-
-    if ((! passwordPolicy.allowMultiplePasswordValues()) && (values.size() > 1))
-    {
-      // FIXME -- What if they're pre-encoded and might all be the same?
-      int    msgID   = MSGID_PWPOLICY_MULTIPLE_PW_VALUES_NOT_ALLOWED;
-      String message = getMessage(msgID, passwordAttribute.getNameOrOID());
-      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
-                                   msgID);
-    }
-
-    CopyOnWriteArrayList<PasswordStorageScheme> defaultStorageSchemes =
-         passwordPolicy.getDefaultStorageSchemes();
-    LinkedHashSet<AttributeValue> newValues =
-         new LinkedHashSet<AttributeValue>(defaultStorageSchemes.size());
-    for (AttributeValue v : values)
-    {
-      ByteString value = v.getValue();
-
-      // See if the password is pre-encoded.
-      if (passwordPolicy.usesAuthPasswordSyntax())
-      {
-        if (AuthPasswordSyntax.isEncoded(value))
-        {
-          if (passwordPolicy.allowPreEncodedPasswords())
-          {
-            newValues.add(v);
-            continue;
-          }
-          else
-          {
-            int    msgID   = MSGID_PWPOLICY_PREENCODED_NOT_ALLOWED;
-            String message = getMessage(msgID,
-                                        passwordAttribute.getNameOrOID());
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-                                         message, msgID);
-          }
-        }
-      }
-      else
-      {
-        if (UserPasswordSyntax.isEncoded(value))
-        {
-          if (passwordPolicy.allowPreEncodedPasswords())
-          {
-            newValues.add(v);
-            continue;
-          }
-          else
-          {
-            int    msgID   = MSGID_PWPOLICY_PREENCODED_NOT_ALLOWED;
-            String message = getMessage(msgID,
-                                        passwordAttribute.getNameOrOID());
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-                                         message, msgID);
-          }
-        }
-      }
-
-
-      // See if the password passes validation.  We should only do this if
-      // validation should be performed for administrators.
-      if (! passwordPolicy.skipValidationForAdministrators())
-      {
-        // There are never any current passwords for an add operation.
-        HashSet<ByteString> currentPasswords = new HashSet<ByteString>(0);
-        StringBuilder invalidReason = new StringBuilder();
-        for (PasswordValidator<?> validator :
-             passwordPolicy.getPasswordValidators().values())
-        {
-          if (! validator.passwordIsAcceptable(value, currentPasswords, this,
-                                               userEntry, invalidReason))
-          {
-            int    msgID   = MSGID_PWPOLICY_VALIDATION_FAILED;
-            String message = getMessage(msgID, passwordAttribute.getNameOrOID(),
-                                        String.valueOf(invalidReason));
-            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
-                                         message, msgID);
-          }
-        }
-      }
-
-
-      // Encode the password.
-      if (passwordPolicy.usesAuthPasswordSyntax())
-      {
-        for (PasswordStorageScheme s : defaultStorageSchemes)
-        {
-          ByteString encodedValue = s.encodeAuthPassword(value);
-          newValues.add(new AttributeValue(passwordAttribute, encodedValue));
-        }
-      }
-      else
-      {
-        for (PasswordStorageScheme s : defaultStorageSchemes)
-        {
-          ByteString encodedValue = s.encodePasswordWithScheme(value);
-          newValues.add(new AttributeValue(passwordAttribute, encodedValue));
-        }
-      }
-    }
-
-
-    // Put the new encoded values in the entry.
-    passwordAttr.setValues(newValues);
-
-
-    // Set the password changed time attribute.
-    ByteString timeString =
-         new ASN1OctetString(TimeThread.getGeneralizedTime());
-    AttributeType changedTimeType =
-         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
-    if (changedTimeType == null)
-    {
-      changedTimeType = DirectoryServer.getDefaultAttributeType(
-                                             OP_ATTR_PWPOLICY_CHANGED_TIME);
-    }
-
-    LinkedHashSet<AttributeValue> changedTimeValues =
-         new LinkedHashSet<AttributeValue>(1);
-    changedTimeValues.add(new AttributeValue(changedTimeType, timeString));
-
-    ArrayList<Attribute> changedTimeList = new ArrayList<Attribute>(1);
-    changedTimeList.add(new Attribute(changedTimeType,
-                                      OP_ATTR_PWPOLICY_CHANGED_TIME,
-                                      changedTimeValues));
-
-    userEntry.putAttribute(changedTimeType, changedTimeList);
-
-
-    // If we should force change on add, then set the appropriate flag.
-    if (passwordPolicy.forceChangeOnAdd())
-    {
-      AttributeType resetType =
-           DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
-      if (resetType == null)
-      {
-        resetType = DirectoryServer.getDefaultAttributeType(
-                                         OP_ATTR_PWPOLICY_RESET_REQUIRED);
-      }
-
-      LinkedHashSet<AttributeValue> resetValues = new
-           LinkedHashSet<AttributeValue>(1);
-      resetValues.add(BooleanSyntax.createBooleanValue(true));
-
-      ArrayList<Attribute> resetList = new ArrayList<Attribute>(1);
-      resetList.add(new Attribute(resetType, OP_ATTR_PWPOLICY_RESET_REQUIRED,
-                                  resetValues));
-      userEntry.putAttribute(resetType, resetList);
-    }
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelResult cancel(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-
-    CancelResult cancelResult = getCancelResult();
-    long stopWaitingTime = System.currentTimeMillis() + 5000;
-    while ((cancelResult == null) &&
-           (System.currentTimeMillis() < stopWaitingTime))
-    {
-      try
-      {
-        Thread.sleep(50);
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-      }
-
-      cancelResult = getCancelResult();
-    }
-
-    if (cancelResult == null)
-    {
-      // This can happen in some rare cases (e.g., if a client disconnects and
-      // there is still a lot of data to send to that client), and in this case
-      // we'll prevent the cancel thread from blocking for a long period of
-      // time.
-      cancelResult = CancelResult.CANNOT_CANCEL;
-    }
-
-    return cancelResult;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelRequest getCancelRequest()
-  {
-    return cancelRequest;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-    return true;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void toString(StringBuilder buffer)
-  {
-    buffer.append("AddOperation(connID=");
-    buffer.append(clientConnection.getConnectionID());
-    buffer.append(", opID=");
-    buffer.append(operationID);
-    buffer.append(", dn=");
-    buffer.append(rawEntryDN);
-    buffer.append(")");
-  }
-}
-
+}
\ No newline at end of file
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/AddOperationBasis.java b/opendj-sdk/opends/src/server/org/opends/server/core/AddOperationBasis.java
new file mode 100644
index 0000000..59a9e79
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/AddOperationBasis.java
@@ -0,0 +1,1024 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+
+import static org.opends.server.config.ConfigConstants.ATTR_OBJECTCLASS;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ENTRY_DN;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ERROR_MESSAGE;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_MATCHED_DN;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_PROCESSING_TIME;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_REFERRAL_URLS;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
+import static org.opends.server.loggers.AccessLogger.logAddRequest;
+import static org.opends.server.loggers.AccessLogger.logAddResponse;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.messages.CoreMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.util.StaticUtils.toLowerCase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.plugin.PreParsePluginResult;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.ldap.LDAPAttribute;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.LDAPException;
+import org.opends.server.types.AbstractOperation;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.ObjectClass;
+import org.opends.server.types.Operation;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.RawAttribute;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.operation.PostResponseAddOperation;
+import org.opends.server.types.operation.PreParseAddOperation;
+import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation;
+
+
+
+
+/**
+ * This class defines an operation that may be used to add a new entry to the
+ * Directory Server.
+ */
+public class AddOperationBasis
+       extends AbstractOperation
+       implements PreParseAddOperation, AddOperation, Runnable,
+                  PostResponseAddOperation
+{
+
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
+
+  // The set of response controls to send to the client.
+  private ArrayList<Control> responseControls;
+
+  // The raw, unprocessed entry DN as provided in the request.  This may or may
+  // not be a valid DN.
+  private ByteString rawEntryDN;
+
+  // The cancel request that has been issued for this add operation.
+  private CancelRequest cancelRequest;
+
+  // The processed DN of the entry to add.
+  private DN entryDN;
+
+  // The proxied authorization target DN for this operation.
+  private DN proxiedAuthorizationDN;
+
+  // The set of attributes (including the objectclass attribute) in a raw,
+  // unprocessed form as provided in the request.  One or more of these
+  // attributes may be invalid.
+  private List<RawAttribute> rawAttributes;
+
+  // The set of operational attributes for the entry to add.
+  private Map<AttributeType,List<Attribute>> operationalAttributes;
+
+  // The set of user attributes for the entry to add.
+  private Map<AttributeType,List<Attribute>> userAttributes;
+
+  // The set of objectclasses for the entry to add.
+  private Map<ObjectClass,String> objectClasses;
+
+  // The change number that has been assigned to this operation.
+  private long changeNumber;
+
+
+  /**
+   * Creates a new add operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  rawEntryDN        The raw DN of the entry to add from the client
+   *                           request.  This may or may not be a valid DN.
+   * @param  rawAttributes     The raw set of attributes from the client
+   *                           request (including the objectclass attribute).
+   *                           This may contain invalid attributes.
+   */
+  public AddOperationBasis(ClientConnection clientConnection, long operationID,
+                      int messageID, List<Control> requestControls,
+                      ByteString rawEntryDN, List<RawAttribute> rawAttributes)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.rawEntryDN    = rawEntryDN;
+    this.rawAttributes = rawAttributes;
+
+    responseControls      = new ArrayList<Control>();
+    cancelRequest         = null;
+    entryDN               = null;
+    userAttributes        = null;
+    operationalAttributes = null;
+    objectClasses         = null;
+    proxiedAuthorizationDN = null;
+    changeNumber          = -1;
+  }
+
+
+
+  /**
+   * Creates a new add operation with the provided information.
+   *
+   * @param  clientConnection       The client connection with which this
+   *                                operation is associated.
+   * @param  operationID            The operation ID for this operation.
+   * @param  messageID              The message ID of the request with which
+   *                                this operation is associated.
+   * @param  requestControls        The set of controls included in the request.
+   * @param  entryDN                The DN for the entry.
+   * @param  objectClasses          The set of objectclasses for the entry.
+   * @param  userAttributes         The set of user attributes for the entry.
+   * @param  operationalAttributes  The set of operational attributes for the
+   *                                entry.
+   */
+  public AddOperationBasis(ClientConnection clientConnection, long operationID,
+                      int messageID, List<Control> requestControls,
+                      DN entryDN, Map<ObjectClass,String> objectClasses,
+                      Map<AttributeType,List<Attribute>> userAttributes,
+                      Map<AttributeType,List<Attribute>> operationalAttributes)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.entryDN               = entryDN;
+    this.objectClasses         = objectClasses;
+    this.userAttributes        = userAttributes;
+    this.operationalAttributes = operationalAttributes;
+
+    rawEntryDN = new ASN1OctetString(entryDN.toString());
+
+    rawAttributes = new ArrayList<RawAttribute>();
+
+    ArrayList<ASN1OctetString> ocValues = new ArrayList<ASN1OctetString>();
+    for (String s : objectClasses.values())
+    {
+      ocValues.add(new ASN1OctetString(s));
+    }
+
+    LDAPAttribute ocAttr = new LDAPAttribute(ATTR_OBJECTCLASS, ocValues);
+    rawAttributes.add(ocAttr);
+
+    for (List<Attribute> attrList : userAttributes.values())
+    {
+      for (Attribute a : attrList)
+      {
+        rawAttributes.add(new LDAPAttribute(a));
+      }
+    }
+
+    for (List<Attribute> attrList : operationalAttributes.values())
+    {
+      for (Attribute a : attrList)
+      {
+        rawAttributes.add(new LDAPAttribute(a));
+      }
+    }
+
+    responseControls = new ArrayList<Control>();
+    proxiedAuthorizationDN = null;
+    cancelRequest    = null;
+    changeNumber     = -1;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ByteString getRawEntryDN()
+  {
+    return rawEntryDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawEntryDN(ByteString rawEntryDN)
+  {
+    this.rawEntryDN = rawEntryDN;
+
+    entryDN = null;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DN getEntryDN()
+  {
+    try
+    {
+      if (entryDN == null)
+      {
+        entryDN = DN.decode(rawEntryDN);
+      }
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      setResultCode(de.getResultCode());
+      appendErrorMessage(de.getErrorMessage());
+      setMatchedDN(de.getMatchedDN());
+      setReferralURLs(de.getReferralURLs());
+    }
+    return entryDN;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final List<RawAttribute> getRawAttributes()
+  {
+    return rawAttributes;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void addRawAttribute(RawAttribute rawAttribute)
+  {
+    rawAttributes.add(rawAttribute);
+
+    objectClasses         = null;
+    userAttributes        = null;
+    operationalAttributes = null;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawAttributes(List<RawAttribute> rawAttributes)
+  {
+    this.rawAttributes = rawAttributes;
+
+    objectClasses         = null;
+    userAttributes        = null;
+    operationalAttributes = null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Map<ObjectClass,String> getObjectClasses()
+  {
+    if (objectClasses == null){
+      computeObjectClassesAndAttributes();
+    }
+    return objectClasses;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void addObjectClass(ObjectClass objectClass, String name)
+  {
+    objectClasses.put(objectClass, name);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void removeObjectClass(ObjectClass objectClass)
+  {
+    objectClasses.remove(objectClass);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Map<AttributeType,List<Attribute>> getUserAttributes()
+  {
+    if (userAttributes == null){
+      computeObjectClassesAndAttributes();
+    }
+    return userAttributes;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Map<AttributeType,List<Attribute>> getOperationalAttributes()
+  {
+    if (operationalAttributes == null){
+      computeObjectClassesAndAttributes();
+    }
+    return operationalAttributes;
+  }
+
+  /**
+   * Build the objectclasses, the user attributes and the operational attributes
+   * if there are not already computed.
+   */
+  private final void computeObjectClassesAndAttributes()
+  {
+    if ((objectClasses == null) || (userAttributes == null) ||
+        (operationalAttributes == null))
+    {
+      objectClasses         = new HashMap<ObjectClass,String>();
+      userAttributes        = new HashMap<AttributeType,List<Attribute>>();
+      operationalAttributes = new HashMap<AttributeType,List<Attribute>>();
+
+      for (RawAttribute a : rawAttributes)
+      {
+        try
+        {
+          Attribute attr = a.toAttribute();
+          AttributeType attrType = attr.getAttributeType();
+
+          // If the attribute type is marked "NO-USER-MODIFICATION" then fail
+          // unless this is an internal operation or is related to
+          // synchronization in some way.
+          if (attrType.isNoUserModification())
+          {
+            if (! (isInternalOperation() || isSynchronizationOperation()))
+            {
+              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+              appendErrorMessage(getMessage(MSGID_ADD_ATTR_IS_NO_USER_MOD,
+                  String.valueOf(entryDN),
+                  attr.getName()));
+
+              objectClasses = null;
+              userAttributes = null;
+              operationalAttributes = null;
+              return;
+            }
+          }
+
+          if (attrType.isObjectClassType())
+          {
+            for (ByteString os : a.getValues())
+            {
+              String ocName = os.toString();
+              ObjectClass oc =
+                DirectoryServer.getObjectClass(toLowerCase(ocName));
+              if (oc == null)
+              {
+                oc = DirectoryServer.getDefaultObjectClass(ocName);
+              }
+
+              objectClasses.put(oc,ocName);
+            }
+          }
+          else if (attrType.isOperational())
+          {
+            List<Attribute> attrs = operationalAttributes.get(attrType);
+            if (attrs == null)
+            {
+              attrs = new ArrayList<Attribute>(1);
+              attrs.add(attr);
+              operationalAttributes.put(attrType, attrs);
+            }
+            else
+            {
+              attrs.add(attr);
+            }
+          }
+          else
+          {
+            List<Attribute> attrs = userAttributes.get(attrType);
+            if (attrs == null)
+            {
+              attrs = new ArrayList<Attribute>(1);
+              attrs.add(attr);
+              userAttributes.put(attrType, attrs);
+            }
+            else
+            {
+              // Check to see if any of the existing attributes in the list
+              // have the same set of options.  If so, then add the values
+              // to that attribute.
+              boolean attributeSeen = false;
+              for (Attribute ea : attrs)
+              {
+                if (ea.optionsEqual(attr.getOptions()))
+                {
+                  LinkedHashSet<AttributeValue> valueSet = ea.getValues();
+                  valueSet.addAll(attr.getValues());
+                  attributeSeen = true;
+                }
+              }
+              if (!attributeSeen)
+              {
+                // This is the first occurrence of the attribute and options.
+                attrs.add(attr);
+              }
+            }
+          }
+        }
+        catch (LDAPException le)
+        {
+          setResultCode(ResultCode.valueOf(le.getResultCode()));
+          appendErrorMessage(le.getMessage());
+
+          objectClasses = null;
+          userAttributes = null;
+          operationalAttributes = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setAttribute(AttributeType attributeType,
+                                 List<Attribute> attributeList)
+  {
+    if (attributeType.isOperational())
+    {
+      if ((attributeList == null) || (attributeList.isEmpty()))
+      {
+        operationalAttributes.remove(attributeType);
+      }
+      else
+      {
+        operationalAttributes.put(attributeType, attributeList);
+      }
+    }
+    else
+    {
+      if ((attributeList == null) || (attributeList.isEmpty()))
+      {
+        userAttributes.remove(attributeType);
+      }
+      else
+      {
+        userAttributes.put(attributeType, attributeList);
+      }
+    }
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void removeAttribute(AttributeType attributeType)
+  {
+    if (attributeType.isOperational())
+    {
+      operationalAttributes.remove(attributeType);
+    }
+    else
+    {
+      userAttributes.remove(attributeType);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getChangeNumber()
+  {
+    return changeNumber;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setChangeNumber(long changeNumber)
+  {
+    this.changeNumber = changeNumber;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final OperationType getOperationType()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    return OperationType.ADD;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void disconnectClient(DisconnectReason disconnectReason,
+                                     boolean sendNotification, String message,
+                                     int messageID)
+  {
+    // Before calling clientConnection.disconnect, we need to mark this
+    // operation as cancelled so that the attempt to cancel it later won't cause
+    // an unnecessary delay.
+    setCancelResult(CancelResult.CANCELED);
+
+    clientConnection.disconnect(disconnectReason, sendNotification, message,
+                                messageID);
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String[][] getRequestLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    return new String[][]
+    {
+      new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) }
+    };
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String[][] getResponseLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    String resultCode = String.valueOf(getResultCode().getIntValue());
+
+    String errorMessage;
+    StringBuilder errorMessageBuffer = getErrorMessage();
+    if (errorMessageBuffer == null)
+    {
+      errorMessage = null;
+    }
+    else
+    {
+      errorMessage = errorMessageBuffer.toString();
+    }
+
+    String matchedDNStr;
+    DN matchedDN = getMatchedDN();
+    if (matchedDN == null)
+    {
+      matchedDNStr = null;
+    }
+    else
+    {
+      matchedDNStr = matchedDN.toString();
+    }
+
+    String referrals;
+    List<String> referralURLs = getReferralURLs();
+    if ((referralURLs == null) || referralURLs.isEmpty())
+    {
+      referrals = null;
+    }
+    else
+    {
+      StringBuilder buffer = new StringBuilder();
+      Iterator<String> iterator = referralURLs.iterator();
+      buffer.append(iterator.next());
+
+      while (iterator.hasNext())
+      {
+        buffer.append(", ");
+        buffer.append(iterator.next());
+      }
+
+      referrals = buffer.toString();
+    }
+
+    String processingTime =
+         String.valueOf(getProcessingTime());
+
+    return new String[][]
+    {
+      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
+      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
+      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
+      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
+      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
+    };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return proxiedAuthorizationDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ArrayList<Control> getResponseControls()
+  {
+    return responseControls;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void addResponseControl(Control control)
+  {
+    responseControls.add(control);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void removeResponseControl(Control control)
+  {
+    responseControls.remove(control);
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final CancelResult cancel(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+
+    CancelResult cancelResult = getCancelResult();
+    long stopWaitingTime = System.currentTimeMillis() + 5000;
+    while ((cancelResult == null) &&
+           (System.currentTimeMillis() < stopWaitingTime))
+    {
+      try
+      {
+        Thread.sleep(50);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+      }
+
+      cancelResult = getCancelResult();
+    }
+
+    if (cancelResult == null)
+    {
+      // This can happen in some rare cases (e.g., if a client disconnects and
+      // there is still a lot of data to send to that client), and in this case
+      // we'll prevent the cancel thread from blocking for a long period of
+      // time.
+      cancelResult = CancelResult.CANNOT_CANCEL;
+    }
+
+    return cancelResult;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final CancelRequest getCancelRequest()
+  {
+    return cancelRequest;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public
+  boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void toString(StringBuilder buffer)
+  {
+    buffer.append("AddOperation(connID=");
+    buffer.append(clientConnection.getConnectionID());
+    buffer.append(", opID=");
+    buffer.append(operationID);
+    buffer.append(", dn=");
+    buffer.append(rawEntryDN);
+    buffer.append(")");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
+  {
+    this.proxiedAuthorizationDN = proxiedAuthorizationDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void run()
+  {
+    // Start the processing timer.
+    setProcessingStartTime();
+    setResultCode(ResultCode.UNDEFINED);
+
+
+    // Check for and handle a request to cancel this operation.
+    if (getCancelRequest() != null)
+    {
+      indicateCancelled(getCancelRequest());
+      setProcessingStopTime();
+      return;
+    }
+
+
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+      DirectoryServer.getPluginConfigManager();
+
+    // Create a labeled block of code that we can break out of if a problem is
+    // detected.
+    addProcessing:
+    {
+      // Invoke the pre-parse add plugins.
+      PreParsePluginResult preParseResult =
+        pluginConfigManager.invokePreParseAddPlugins(this);
+      if (preParseResult.connectionTerminated())
+      {
+        // There's no point in continuing with anything.  Log the request and
+        // result and return.
+        setResultCode(ResultCode.CANCELED);
+
+        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
+        appendErrorMessage(getMessage(msgID));
+
+        setProcessingStopTime();
+
+        logAddRequest(this);
+        logAddResponse(this);
+        pluginConfigManager.invokePostResponseAddPlugins(this);
+        return;
+      }
+      else if (preParseResult.sendResponseImmediately())
+      {
+        logAddRequest(this);
+        break addProcessing;
+      }
+      else if (preParseResult.skipCoreProcessing())
+      {
+        break addProcessing;
+      }
+
+      // Log the add request message.
+      logAddRequest(this);
+
+
+      // Check for and handle a request to cancel this operation.
+      if (getCancelRequest() != null)
+      {
+        indicateCancelled(getCancelRequest());
+        setProcessingStopTime();
+        logAddResponse(this);
+        pluginConfigManager.invokePostResponseAddPlugins(this);
+        return;
+      }
+
+
+      // Process the entry DN and set of attributes to convert them from their
+      // raw forms as provided by the client to the forms required for the rest
+      // of the add processing.
+      DN entryDN = getEntryDN();
+      if (entryDN == null){
+        break addProcessing;
+      }
+
+
+      // Retrieve the network group attached to the client connection
+      // and get a workflow to process the operation.
+      NetworkGroup ng = getClientConnection().getNetworkGroup();
+      Workflow workflow = ng.getWorkflowCandidate(entryDN);
+      if (workflow == null)
+      {
+        // We have found no workflow for the requested base DN, just return
+        // a no such entry result code and stop the processing.
+        updateOperationErrMsgAndResCode();
+        break addProcessing;
+      }
+      workflow.execute(this);
+    }
+
+    // Check for and handle a request to cancel this operation.
+    if ((getCancelRequest() != null) ||
+        (getCancelResult() == CancelResult.CANCELED))
+    {
+      if (getCancelRequest() != null){
+        indicateCancelled(getCancelRequest());
+      }
+      setProcessingStopTime();
+      logAddResponse(this);
+      invokePostResponsePlugins();
+      return;
+    }
+
+    // Indicate that it is now too late to attempt to cancel the operation.
+    setCancelResult(CancelResult.TOO_LATE);
+
+    // Stop the processing timer.
+    setProcessingStopTime();
+
+    // Send the add response to the client.
+    getClientConnection().sendResponse(this);
+
+    // Log the add response.
+    logAddResponse(this);
+
+    // Check wether there are local operations in attachments
+    List localOperations =
+      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
+    if (localOperations != null && (! localOperations.isEmpty())){
+      for (Object localOp : localOperations)
+      {
+        LocalBackendAddOperation localOperation =
+          (LocalBackendAddOperation)localOp;
+        // Notify any persistent searches that might be registered with the
+        // server.
+        if ((getResultCode() == ResultCode.SUCCESS) &&
+            (localOperation.getEntryToAdd() != null))
+        {
+          for (PersistentSearch persistentSearch :
+            DirectoryServer.getPersistentSearches())
+          {
+            try
+            {
+              persistentSearch.processAdd(localOperation,
+                  localOperation.getEntryToAdd());
+            }
+            catch (Exception e)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, e);
+              }
+
+              int    msgID   = MSGID_ADD_ERROR_NOTIFYING_PERSISTENT_SEARCH;
+              String message = getMessage(msgID,
+                  String.valueOf(persistentSearch),
+                  getExceptionMessage(e));
+              logError(ErrorLogCategory.CORE_SERVER,
+                  ErrorLogSeverity.SEVERE_ERROR,
+                  message, msgID);
+
+              DirectoryServer.deregisterPersistentSearch(persistentSearch);
+            }
+          }
+        }
+
+        // Invoke the post-response add plugins.
+        pluginConfigManager.invokePostResponseAddPlugins(localOperation);
+      }
+    }
+  }
+
+  /**
+   * Updates the error message and the result code of the operation.
+   *
+   * This method is called because no workflows were found to process
+   * the operation.
+   */
+  private void updateOperationErrMsgAndResCode()
+  {
+    DN entryDN = getEntryDN();
+    DN parentDN = entryDN.getParentDNInSuffix();
+    if (parentDN == null)
+    {
+      // Either this entry is a suffix or doesn't belong in the directory.
+      if (DirectoryServer.isNamingContext(entryDN))
+      {
+        // This is fine.  This entry is one of the configured suffixes.
+      }
+      else if (entryDN.isNullDN())
+      {
+        // This is not fine.  The root DSE cannot be added.
+        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+        appendErrorMessage(getMessage(MSGID_ADD_CANNOT_ADD_ROOT_DSE));
+      }
+      else
+      {
+        // The entry doesn't have a parent but isn't a suffix.  This is not
+        // allowed.
+        setResultCode(ResultCode.NO_SUCH_OBJECT);
+        appendErrorMessage(getMessage(MSGID_ADD_ENTRY_NOT_SUFFIX,
+                                      String.valueOf(entryDN)));
+      }
+    }
+  }
+
+  /**
+   * Execute the postResponseAddPlugins.
+   */
+  private void invokePostResponsePlugins()
+  {
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+      DirectoryServer.getPluginConfigManager();
+
+    // Check wether there are local operations in attachments
+    List localOperations =
+      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
+
+    if (localOperations != null && (! localOperations.isEmpty()))
+    {
+      for (Object localOp : localOperations)
+      {
+        LocalBackendAddOperation localOperation =
+          (LocalBackendAddOperation)localOp;
+        // Invoke the post-response add plugins.
+        pluginConfigManager.invokePostResponseAddPlugins(localOperation);
+      }
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * This method always returns null.
+   */
+  public Entry getEntryToAdd()
+  {
+    return null;
+  }
+
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/AddOperationWrapper.java b/opendj-sdk/opends/src/server/org/opends/server/core/AddOperationWrapper.java
new file mode 100644
index 0000000..7497bb6
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/AddOperationWrapper.java
@@ -0,0 +1,653 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ObjectClass;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.RawAttribute;
+import org.opends.server.types.ResultCode;
+
+/**
+ * This abstract class wraps/decorates a given add operation.
+ * This class will be extended by sub-classes to enhance the
+ * functionnality of the AddOperationBasis.
+ */
+public abstract class AddOperationWrapper implements AddOperation
+{
+  private AddOperation add;
+
+  /**
+   * Creates a new add operation based on the provided add operation.
+   *
+   * @param add The add operation to wrap
+   */
+  public AddOperationWrapper(AddOperation add){
+    this.add = add;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addObjectClass(ObjectClass objectClass, String name)
+  {
+    add.addObjectClass(objectClass, name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addRawAttribute(RawAttribute rawAttribute)
+  {
+    add.addRawAttribute(rawAttribute);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addRequestControl(Control control)
+  {
+    add.addRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addResponseControl(Control control)
+  {
+    add.addResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendAdditionalLogMessage(String message)
+  {
+    add.appendAdditionalLogMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendErrorMessage(String message)
+  {
+    add.appendErrorMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult cancel(CancelRequest cancelRequest)
+  {
+    return add.cancel(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void disconnectClient(DisconnectReason disconnectReason,
+      boolean sendNotification, String message, int messageID)
+  {
+    add.disconnectClient(disconnectReason, sendNotification, message,
+        messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean dontSynchronize()
+  {
+    return add.dontSynchronize();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getAdditionalLogMessage()
+  {
+    return add.getAdditionalLogMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object getAttachment(String name)
+  {
+    return add.getAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<String, Object> getAttachments()
+  {
+    return add.getAttachments();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getAuthorizationDN()
+  {
+    return add.getAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry getAuthorizationEntry()
+  {
+    return add.getAuthorizationEntry();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelRequest getCancelRequest()
+  {
+    return add.getCancelRequest();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult getCancelResult()
+  {
+    return add.getCancelResult();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getChangeNumber()
+  {
+    return add.getChangeNumber();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ClientConnection getClientConnection()
+  {
+    return add.getClientConnection();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getCommonLogElements()
+  {
+    return add.getCommonLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getConnectionID()
+  {
+    return add.getConnectionID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getEntryDN()
+  {
+    return add.getEntryDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getErrorMessage()
+  {
+    return add.getErrorMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getMatchedDN()
+  {
+    return add.getMatchedDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getMessageID()
+  {
+    return add.getMessageID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<ObjectClass, String> getObjectClasses()
+  {
+    return add.getObjectClasses();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<AttributeType, List<Attribute>> getOperationalAttributes()
+  {
+    return add.getOperationalAttributes();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getOperationID()
+  {
+    return add.getOperationID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public OperationType getOperationType()
+  {
+    return add.getOperationType();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStartTime()
+  {
+    return add.getProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStopTime()
+  {
+    return add.getProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingTime()
+  {
+    return add.getProcessingTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<RawAttribute> getRawAttributes()
+  {
+    return add.getRawAttributes();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getRawEntryDN()
+  {
+    return add.getRawEntryDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<String> getReferralURLs()
+  {
+    return add.getReferralURLs();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getRequestControls()
+  {
+    return add.getRequestControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getRequestLogElements()
+  {
+    return add.getRequestLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getResponseControls()
+  {
+    return add.getResponseControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getResponseLogElements()
+  {
+    return add.getResponseLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ResultCode getResultCode()
+  {
+    return add.getResultCode();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<AttributeType, List<Attribute>> getUserAttributes()
+  {
+    return add.getUserAttributes();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void indicateCancelled(CancelRequest cancelRequest)
+  {
+    add.indicateCancelled(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isInternalOperation()
+  {
+    return add.isInternalOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isSynchronizationOperation()
+  {
+    return add.isSynchronizationOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void operationCompleted()
+  {
+    add.operationCompleted();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object removeAttachment(String name)
+  {
+    return add.removeAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeAttribute(AttributeType attributeType)
+  {
+    add.removeAttribute(attributeType);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeObjectClass(ObjectClass objectClass)
+  {
+    add.removeObjectClass(objectClass);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeRequestControl(Control control)
+  {
+    add.removeRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeResponseControl(Control control)
+  {
+    add.removeResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAdditionalLogMessage(StringBuilder additionalLogMessage)
+  {
+    add.setAdditionalLogMessage(additionalLogMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object setAttachment(String name, Object value)
+  {
+    return add.setAttachment(name, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttachments(Map<String, Object> attachments)
+  {
+    add.setAttachments(attachments);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttribute(AttributeType attributeType,
+      List<Attribute> attributeList)
+  {
+    add.setAttribute(attributeType, attributeList);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAuthorizationEntry(Entry authorizationEntry)
+  {
+    add.setAuthorizationEntry(authorizationEntry);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    return add.setCancelRequest(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setCancelResult(CancelResult cancelResult)
+  {
+    add.setCancelResult(cancelResult);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setChangeNumber(long changeNumber)
+  {
+    add.setChangeNumber(changeNumber);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setDontSynchronize(boolean dontSynchronize)
+  {
+    add.setDontSynchronize(dontSynchronize);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setErrorMessage(StringBuilder errorMessage)
+  {
+    add.setErrorMessage(errorMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setInternalOperation(boolean isInternalOperation)
+  {
+    add.setInternalOperation(isInternalOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setMatchedDN(DN matchedDN)
+  {
+    add.setMatchedDN(matchedDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStartTime()
+  {
+    add.setProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStopTime()
+  {
+    add.setProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawAttributes(List<RawAttribute> rawAttributes)
+  {
+    add.setRawAttributes(rawAttributes);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawEntryDN(ByteString rawEntryDN)
+  {
+    add.setRawEntryDN(rawEntryDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setReferralURLs(List<String> referralURLs)
+  {
+    add.setReferralURLs(referralURLs);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResponseData(DirectoryException directoryException)
+  {
+    add.setResponseData(directoryException);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResultCode(ResultCode resultCode)
+  {
+    add.setResultCode(resultCode);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSynchronizationOperation(boolean isSynchronizationOperation)
+  {
+    add.setSynchronizationOperation(isSynchronizationOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return add.toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void toString(StringBuilder buffer)
+  {
+    add.toString(buffer);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return add.getProxiedAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
+  {
+    add.setProxiedAuthorizationDN(proxiedAuthorizationDN);
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java
index a62bff4..03cf58b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java
@@ -26,414 +26,33 @@
  */
 package org.opends.server.core;
 
-
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.concurrent.locks.Lock;
-
-import org.opends.server.api.ClientConnection;
-import org.opends.server.api.SASLMechanismHandler;
-import org.opends.server.api.plugin.PostOperationPluginResult;
-import org.opends.server.api.plugin.PreOperationPluginResult;
-import org.opends.server.api.plugin.PreParsePluginResult;
-import org.opends.server.controls.AuthorizationIdentityResponseControl;
-import org.opends.server.controls.PasswordExpiredControl;
-import org.opends.server.controls.PasswordExpiringControl;
-import org.opends.server.controls.PasswordPolicyErrorType;
-import org.opends.server.controls.PasswordPolicyResponseControl;
-import org.opends.server.controls.PasswordPolicyWarningType;
 import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.types.AccountStatusNotificationType;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.AttributeValue;
 import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.AuthenticationType;
 import org.opends.server.types.ByteString;
-import org.opends.server.types.CancelRequest;
-import org.opends.server.types.CancelResult;
-import org.opends.server.types.Control;
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
-import org.opends.server.types.ErrorLogCategory;
-import org.opends.server.types.ErrorLogSeverity;
-import org.opends.server.types.LockManager;
 import org.opends.server.types.Operation;
-import org.opends.server.types.OperationType;
-import org.opends.server.types.ResultCode;
-import org.opends.server.types.operation.PostOperationBindOperation;
-import org.opends.server.types.operation.PostResponseBindOperation;
-import org.opends.server.types.operation.PreOperationBindOperation;
-import org.opends.server.types.operation.PreParseBindOperation;
-
-import static org.opends.server.config.ConfigConstants.*;
-import static org.opends.server.core.CoreConstants.*;
-import static org.opends.server.loggers.AccessLogger.*;
-import static org.opends.server.loggers.ErrorLogger.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
-import org.opends.server.types.DebugLogLevel;
-import static org.opends.server.messages.CoreMessages.*;
-import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
-
-
 
 /**
- * This class defines an operation that may be used to authenticate a user to
- * the Directory Server.  Note that for security restrictions, response messages
- * that may be returned to the client must be carefully cleaned to ensure that
- * they do not provide a malicious client with information that may be useful in
- * an attack.  This does impact the debugability of the server, but that can
- * be addressed by calling the <CODE>setAuthFailureReason</CODE> method, which
- * can provide a reason for a failure in a form that will not be returned to the
- * client but may be written to a log file.
+ * This interface defines an operation that may be used to authenticate a user
+ * to the Directory Server.  Note that for security restrictions, response
+ * messages that may be returned to the client must be carefully cleaned to
+ * ensure that they do not provide a malicious client with information that may
+ * be useful in an attack.  This does impact the debugability of the server,
+ * but that can be addressed by calling the <CODE>setAuthFailureReason</CODE>
+ * method, which can provide a reason for a failure in a form that will not be
+ * returned to the client but may be written to a log file.
  */
-public class BindOperation
-             extends Operation
-             implements PreParseBindOperation, PreOperationBindOperation,
-                        PostOperationBindOperation, PostResponseBindOperation
+public interface BindOperation extends Operation
 {
-  /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = getTracer();
-
-  // The credentials used for SASL authentication.
-  private ASN1OctetString saslCredentials;
-
-  // The server SASL credentials provided to the client in the response.
-  private ASN1OctetString serverSASLCredentials;
-
-  // The authentication info for this bind operation.
-  private AuthenticationInfo authInfo;
-
-  // The authentication type used for this bind operation.
-  private AuthenticationType authType;
-
-  // Indicates whether the warning notification that should be sent to the user
-  // would be the first warning.
-  private boolean isFirstWarning;
-
-  // Indicates whether the authentication should use a grace login if it is
-  // successful.
-  private boolean isGraceLogin;
-
-  // Indicates whether the user's password must be changed before any other
-  // operations will be allowed.
-  private boolean mustChangePassword;
-
-  // Indicates whether the client included the password policy control in the
-  // bind request.
-  private boolean pwPolicyControlRequested;
-
-  // The raw, unprocessed bind DN as contained in the client request.
-  private ByteString rawBindDN;
-
-  // The password used for simple authentication.
-  private ByteString simplePassword;
-
-  // The bind DN used for this bind operation.
-  private DN bindDN;
-
-  // The DN of the user entry that is attempting to authenticate.
-  private DN userEntryDN;
-
-  // The entry of the user that successfully authenticated during processing of
-  // this bind operation.
-  private Entry authenticatedUserEntry;
-
-  // The DN of the user as whom a SASL authentication was attempted (regardless
-  // of whether the authentication was successful) for the purpose of updating
-  // password policy state information.
-  private Entry saslAuthUserEntry;
-
-  // The unique ID associated with the failure reason message.
-  private int authFailureID;
-
-  // The password policy warning value that should be included in the response
-  // control.
-  private int pwPolicyWarningValue;
-
-  // The set of response controls for this bind operation.
-  private List<Control> responseControls;
-
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
-  // The password policy error type that should be included in the response
-  // control
-  private PasswordPolicyErrorType pwPolicyErrorType;
-
-  // The password policy warning type that should be included in the response
-  // control
-  private PasswordPolicyWarningType pwPolicyWarningType;
-
-  // The password policy state information for this bind operation.
-  private PasswordPolicyState pwPolicyState;
-
-  // A message explaining the reason for the authentication failure.
-  private String authFailureReason;
-
-  // A string representation of the protocol version for this bind operation.
-  private String protocolVersion;
-
-  // The SASL mechanism used for SASL authentication.
-  private String saslMechanism;
-
-
-
-  /**
-   * Creates a new simple bind operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  protocolVersion   The string representation of the protocol version
-   *                           associated with this bind request.
-   * @param  rawBindDN         The raw, unprocessed bind DN as provided in the
-   *                           request from the client.
-   * @param  simplePassword    The password to use for the simple
-   *                           authentication.
-   */
-  public BindOperation(ClientConnection clientConnection, long operationID,
-                       int messageID, List<Control> requestControls,
-                       String protocolVersion, ByteString rawBindDN,
-                       ByteString simplePassword)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.protocolVersion = protocolVersion;
-    this.authType        = AuthenticationType.SIMPLE;
-    this.saslMechanism   = null;
-    this.saslCredentials = null;
-
-    if (rawBindDN == null)
-    {
-      this.rawBindDN = new ASN1OctetString();
-    }
-    else
-    {
-      this.rawBindDN = rawBindDN;
-    }
-
-    if (simplePassword == null)
-    {
-      this.simplePassword = new ASN1OctetString();
-    }
-    else
-    {
-      this.simplePassword = simplePassword;
-    }
-
-    bindDN                   = null;
-    userEntryDN              = null;
-    responseControls         = new ArrayList<Control>(0);
-    authFailureID            = 0;
-    authFailureReason        = null;
-    authenticatedUserEntry   = null;
-    saslAuthUserEntry        = null;
-    isFirstWarning           = false;
-    isGraceLogin             = false;
-    mustChangePassword       = false;
-    pwPolicyControlRequested = false;
-    pwPolicyErrorType        = null;
-    pwPolicyWarningType      = null;
-    pwPolicyWarningValue     = -1;
-  }
-
-
-
-  /**
-   * Creates a new SASL bind operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  protocolVersion   The string representation of the protocol version
-   *                           associated with this bind request.
-   * @param  rawBindDN         The raw, unprocessed bind DN as provided in the
-   *                           request from the client.
-   * @param  saslMechanism     The SASL mechanism included in the request.
-   * @param  saslCredentials   The optional SASL credentials included in the
-   *                           request.
-   */
-  public BindOperation(ClientConnection clientConnection, long operationID,
-                       int messageID, List<Control> requestControls,
-                       String protocolVersion, ByteString rawBindDN,
-                       String saslMechanism, ASN1OctetString saslCredentials)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.protocolVersion = protocolVersion;
-    this.authType        = AuthenticationType.SASL;
-    this.saslMechanism   = saslMechanism;
-    this.saslCredentials = saslCredentials;
-    this.simplePassword  = null;
-
-    if (rawBindDN == null)
-    {
-      this.rawBindDN = new ASN1OctetString();
-    }
-    else
-    {
-      this.rawBindDN = rawBindDN;
-    }
-
-    bindDN                 = null;
-    userEntryDN            = null;
-    responseControls       = new ArrayList<Control>(0);
-    authFailureID          = 0;
-    authFailureReason      = null;
-    authenticatedUserEntry = null;
-    saslAuthUserEntry      = null;
-  }
-
-
-
-  /**
-   * Creates a new simple bind operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  protocolVersion   The string representation of the protocol version
-   *                           associated with this bind request.
-   * @param  bindDN            The bind DN for this bind operation.
-   * @param  simplePassword    The password to use for the simple
-   *                           authentication.
-   */
-  public BindOperation(ClientConnection clientConnection, long operationID,
-                       int messageID, List<Control> requestControls,
-                       String protocolVersion, DN bindDN,
-                       ByteString simplePassword)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.protocolVersion = protocolVersion;
-    this.authType        = AuthenticationType.SIMPLE;
-    this.bindDN          = bindDN;
-    this.saslMechanism   = null;
-    this.saslCredentials = null;
-
-    if (bindDN == null)
-    {
-      rawBindDN = new ASN1OctetString();
-    }
-    else
-    {
-      rawBindDN = new ASN1OctetString(bindDN.toString());
-    }
-
-    if (simplePassword == null)
-    {
-      this.simplePassword = new ASN1OctetString();
-    }
-    else
-    {
-      this.simplePassword = simplePassword;
-    }
-
-    responseControls         = new ArrayList<Control>(0);
-    authFailureID            = 0;
-    authFailureReason        = null;
-    authenticatedUserEntry   = null;
-    saslAuthUserEntry        = null;
-    isFirstWarning           = false;
-    isGraceLogin             = false;
-    mustChangePassword       = false;
-    pwPolicyControlRequested = false;
-    pwPolicyErrorType        = null;
-    pwPolicyWarningType      = null;
-    pwPolicyWarningValue     = -1;
-    userEntryDN              = null;
-  }
-
-
-
-  /**
-   * Creates a new SASL bind operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  protocolVersion   The string representation of the protocol version
-   *                           associated with this bind request.
-   * @param  bindDN            The bind DN for this bind operation.
-   * @param  saslMechanism     The SASL mechanism included in the request.
-   * @param  saslCredentials   The optional SASL credentials included in the
-   *                           request.
-   */
-  public BindOperation(ClientConnection clientConnection, long operationID,
-                       int messageID, List<Control> requestControls,
-                       String protocolVersion, DN bindDN,
-                       String saslMechanism, ASN1OctetString saslCredentials)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.protocolVersion = protocolVersion;
-    this.authType        = AuthenticationType.SASL;
-    this.bindDN          = bindDN;
-    this.saslMechanism   = saslMechanism;
-    this.saslCredentials = saslCredentials;
-    this.simplePassword  = null;
-
-    if (bindDN == null)
-    {
-      rawBindDN = new ASN1OctetString();
-    }
-    else
-    {
-      rawBindDN = new ASN1OctetString(bindDN.toString());
-    }
-
-    responseControls       = new ArrayList<Control>(0);
-    authFailureID          = 0;
-    authFailureReason      = null;
-    authenticatedUserEntry = null;
-    saslAuthUserEntry      = null;
-    userEntryDN            = null;
-  }
-
-
 
   /**
    * Retrieves the authentication type for this bind operation.
    *
    * @return  The authentication type for this bind operation.
    */
-  public final AuthenticationType getAuthenticationType()
-  {
-    return authType;
-  }
-
-
+  public abstract AuthenticationType getAuthenticationType();
 
   /**
    * Retrieves the raw, unprocessed bind DN for this bind operation as contained
@@ -443,12 +62,7 @@
    * @return  The raw, unprocessed bind DN for this bind operation as contained
    *          in the client request.
    */
-  public final ByteString getRawBindDN()
-  {
-    return rawBindDN;
-  }
-
-
+  public abstract ByteString getRawBindDN();
 
   /**
    * Specifies the raw, unprocessed bind DN for this bind operation.  This
@@ -456,21 +70,7 @@
    *
    * @param  rawBindDN  The raw, unprocessed bind DN for this bind operation.
    */
-  public final void setRawBindDN(ByteString rawBindDN)
-  {
-    if (rawBindDN == null)
-    {
-      this.rawBindDN = new ASN1OctetString();
-    }
-    else
-    {
-      this.rawBindDN = rawBindDN;
-    }
-
-    bindDN = null;
-  }
-
-
+  public abstract void setRawBindDN(ByteString rawBindDN);
 
   /**
    * Retrieves a string representation of the protocol version associated with
@@ -479,12 +79,7 @@
    * @return  A string representation of the protocol version associated with
    *          this bind request.
    */
-  public String getProtocolVersion()
-  {
-    return protocolVersion;
-  }
-
-
+  public String getProtocolVersion();
 
   /**
    * Specifies the string representation of the protocol version associated with
@@ -493,12 +88,7 @@
    * @param  protocolVersion  The string representation of the protocol version
    *                          associated with this bind request.
    */
-  public void setProtocolVersion(String protocolVersion)
-  {
-    this.protocolVersion = protocolVersion;
-  }
-
-
+  public void setProtocolVersion(String protocolVersion);
 
   /**
    * Retrieves the bind DN for this bind operation.  This method should not be
@@ -509,24 +99,14 @@
    * @return  The bind DN for this bind operation, or <CODE>null</CODE> if the
    *          raw DN has not yet been processed.
    */
-  public final DN getBindDN()
-  {
-    return bindDN;
-  }
-
-
+  public abstract DN getBindDN();
 
   /**
    * Retrieves the simple authentication password for this bind operation.
    *
    * @return  The simple authentication password for this bind operation.
    */
-  public final ByteString getSimplePassword()
-  {
-    return simplePassword;
-  }
-
-
+  public abstract ByteString getSimplePassword();
 
   /**
    * Specifies the simple authentication password for this bind operation.
@@ -534,23 +114,7 @@
    * @param  simplePassword  The simple authentication password for this bind
    *                         operation.
    */
-  public final void setSimplePassword(ByteString simplePassword)
-  {
-    if (simplePassword == null)
-    {
-      this.simplePassword = new ASN1OctetString();
-    }
-    else
-    {
-      this.simplePassword = simplePassword;
-    }
-
-    authType        = AuthenticationType.SIMPLE;
-    saslMechanism   = null;
-    saslCredentials = null;
-  }
-
-
+  public abstract void setSimplePassword(ByteString simplePassword);
 
   /**
    * Retrieves the SASL mechanism for this bind operation.
@@ -558,12 +122,7 @@
    * @return  The SASL mechanism for this bind operation, or <CODE>null</CODE>
    *          if the bind does not use SASL authentication.
    */
-  public final String getSASLMechanism()
-  {
-    return  saslMechanism;
-  }
-
-
+  public abstract String getSASLMechanism();
 
   /**
    * Retrieves the SASL credentials for this bind operation.
@@ -571,12 +130,7 @@
    * @return  The SASL credentials for this bind operation, or <CODE>null</CODE>
    *          if there are none or if the bind does not use SASL authentication.
    */
-  public final ASN1OctetString getSASLCredentials()
-  {
-    return saslCredentials;
-  }
-
-
+  public abstract ASN1OctetString getSASLCredentials();
 
   /**
    * Specifies the SASL credentials for this bind operation.
@@ -585,17 +139,8 @@
    * @param  saslCredentials  The SASL credentials for this bind operation, or
    *                          <CODE>null</CODE> if there are none.
    */
-  public final void setSASLCredentials(String saslMechanism,
-                                       ASN1OctetString saslCredentials)
-  {
-    this.saslMechanism   = saslMechanism;
-    this.saslCredentials = saslCredentials;
-
-    authType       = AuthenticationType.SASL;
-    simplePassword = null;
-  }
-
-
+  public abstract void setSASLCredentials(String saslMechanism,
+      ASN1OctetString saslCredentials);
 
   /**
    * Retrieves the set of server SASL credentials to include in the bind
@@ -604,12 +149,7 @@
    * @return  The set of server SASL credentials to include in the bind
    *          response, or <CODE>null</CODE> if there are none.
    */
-  public final ASN1OctetString getServerSASLCredentials()
-  {
-    return serverSASLCredentials;
-  }
-
-
+  public abstract ASN1OctetString getServerSASLCredentials();
 
   /**
    * Specifies the set of server SASL credentials to include in the bind
@@ -618,13 +158,8 @@
    * @param  serverSASLCredentials  The set of server SASL credentials to
    *                                include in the bind response.
    */
-  public final void setServerSASLCredentials(ASN1OctetString
-                                                  serverSASLCredentials)
-  {
-    this.serverSASLCredentials = serverSASLCredentials;
-  }
-
-
+  public abstract void setServerSASLCredentials(
+      ASN1OctetString serverSASLCredentials);
 
   /**
    * Retrieves the user entry associated with the SASL authentication attempt.
@@ -636,12 +171,7 @@
    *          <CODE>null</CODE> if it was not a SASL authentication or the SASL
    *          processing was not able to map the request to a user.
    */
-  public final Entry getSASLAuthUserEntry()
-  {
-    return saslAuthUserEntry;
-  }
-
-
+  public abstract Entry getSASLAuthUserEntry();
 
   /**
    * Specifies the user entry associated with the SASL authentication attempt.
@@ -652,12 +182,7 @@
    * @param  saslAuthUserEntry  The user entry associated with the SASL
    *                            authentication attempt.
    */
-  public final void setSASLAuthUserEntry(Entry saslAuthUserEntry)
-  {
-    this.saslAuthUserEntry = saslAuthUserEntry;
-  }
-
-
+  public abstract void setSASLAuthUserEntry(Entry saslAuthUserEntry);
 
   /**
    * Retrieves a human-readable message providing the reason that the
@@ -666,12 +191,7 @@
    * @return  A human-readable message providing the reason that the
    *          authentication failed, or <CODE>null</CODE> if none is available.
    */
-  public final String getAuthFailureReason()
-  {
-    return authFailureReason;
-  }
-
-
+  public abstract String getAuthFailureReason();
 
   /**
    * Retrieves the unique identifier for the authentication failure reason, if
@@ -680,12 +200,7 @@
    * @return  The unique identifier for the authentication failure reason, or
    *          zero if none is available.
    */
-  public final int getAuthFailureID()
-  {
-    return authFailureID;
-  }
-
-
+  public abstract int getAuthFailureID();
 
   /**
    * Specifies the reason that the authentication failed.
@@ -695,21 +210,7 @@
    * @param  reason  A human-readable message providing the reason that the
    *                 authentication failed.
    */
-  public final void setAuthFailureReason(int id, String reason)
-  {
-    if (id < 0)
-    {
-      authFailureID = 0;
-    }
-    else
-    {
-      authFailureID = id;
-    }
-
-    authFailureReason = reason;
-  }
-
-
+  public abstract void setAuthFailureReason(int id, String reason);
 
   /**
    * Retrieves the user entry DN for this bind operation.  It will only be
@@ -720,12 +221,7 @@
    *          the bind processing has not progressed far enough to identify the
    *          user or if the user DN could not be determined.
    */
-  public final DN getUserEntryDN()
-  {
-    return userEntryDN;
-  }
-
-
+  public abstract DN getUserEntryDN();
 
   /**
    * Retrieves the authentication info that resulted from processing this bind
@@ -734,12 +230,7 @@
    * @return  The authentication info that resulted from processing this bind
    *          operation.
    */
-  public final AuthenticationInfo getAuthenticationInfo()
-  {
-    return authInfo;
-  }
-
-
+  public abstract AuthenticationInfo getAuthenticationInfo();
 
   /**
    * Specifies the authentication info that resulted from processing this bind
@@ -749,1674 +240,17 @@
    * @param  authInfo  The authentication info that resulted from processing
    *                   this bind operation.
    */
-  public final void setAuthenticationInfo(AuthenticationInfo authInfo)
-  {
-    this.authInfo = authInfo;
-  }
-
-
+  public abstract void setAuthenticationInfo(AuthenticationInfo authInfo);
 
   /**
-   * {@inheritDoc}
+   * Set the user entry DN for this bind operation.
+   *
+   * @param  userEntryDN  The user entry DN for this bind operation, or
+   *                      <CODE>null</CODE> if the bind processing has not
+   *                      progressed far enough to identify the user or if
+   *                      the user DN could not be determined.
    */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final OperationType getOperationType()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    return OperationType.BIND;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void disconnectClient(DisconnectReason disconnectReason,
-                                     boolean sendNotification, String message,
-                                     int messageID)
-  {
-    // Since bind operations can't be cancelled, we don't need to do anything
-    // but forward the request on to the client connection.
-    clientConnection.disconnect(disconnectReason, sendNotification, message,
-                                messageID);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final String[][] getRequestLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    if (authType == AuthenticationType.SASL)
-    {
-      return new String[][]
-      {
-        new String[] { LOG_ELEMENT_BIND_DN, String.valueOf(rawBindDN) },
-        new String[] { LOG_ELEMENT_AUTH_TYPE, authType.toString() },
-        new String[] { LOG_ELEMENT_SASL_MECHANISM, saslMechanism }
-      };
-    }
-    else
-    {
-      return new String[][]
-      {
-        new String[] { LOG_ELEMENT_BIND_DN, String.valueOf(rawBindDN) },
-        new String[] { LOG_ELEMENT_AUTH_TYPE, authType.toString() }
-      };
-    }
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final String[][] getResponseLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    String resultCode = String.valueOf(getResultCode().getIntValue());
-
-    String errorMessage;
-    StringBuilder errorMessageBuffer = getErrorMessage();
-    if (errorMessageBuffer == null)
-    {
-      errorMessage = null;
-    }
-    else
-    {
-      errorMessage = errorMessageBuffer.toString();
-    }
-
-    String matchedDNStr;
-    DN matchedDN = getMatchedDN();
-    if (matchedDN == null)
-    {
-      matchedDNStr = null;
-    }
-    else
-    {
-      matchedDNStr = matchedDN.toString();
-    }
-
-    String referrals;
-    List<String> referralURLs = getReferralURLs();
-    if ((referralURLs == null) || referralURLs.isEmpty())
-    {
-      referrals = null;
-    }
-    else
-    {
-      StringBuilder buffer = new StringBuilder();
-      Iterator<String> iterator = referralURLs.iterator();
-      buffer.append(iterator.next());
-
-      while (iterator.hasNext())
-      {
-        buffer.append(", ");
-        buffer.append(iterator.next());
-      }
-
-      referrals = buffer.toString();
-    }
-
-    String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
-      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
-      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
-      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
-      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
-    };
-  }
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final List<Control> getResponseControls()
-  {
-    return responseControls;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void addResponseControl(Control control)
-  {
-    responseControls.add(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void removeResponseControl(Control control)
-  {
-    responseControls.remove(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void run()
-  {
-    // Start the processing timer and initially set the result to indicate that
-    // the result is unknown.
-    processingStartTime = System.currentTimeMillis();
-    setResultCode(ResultCode.UNDEFINED);
-    boolean returnAuthzID    = false;
-    int     sizeLimit        = DirectoryServer.getSizeLimit();
-    int     timeLimit        = DirectoryServer.getTimeLimit();
-    int     lookthroughLimit = DirectoryServer.getLookthroughLimit();
-
-
-    // Set a flag to indicate that a bind operation is in progress.  This should
-    // ensure that no new operations will be accepted for this client until the
-    // bind is complete.
-    clientConnection.setBindInProgress(true);
-
-
-    // Wipe out any existing authentication for the client connection and create
-    // a placeholder that will be used if the bind is successful.
-    clientConnection.setUnauthenticated();
-    authInfo = null;
-
-
-    // Abandon any operations that may be in progress for the client.
-    String cancelReason = getMessage(MSGID_CANCELED_BY_BIND_REQUEST);
-    CancelRequest cancelRequest = new CancelRequest(true, cancelReason);
-    clientConnection.cancelAllOperationsExcept(cancelRequest, getMessageID());
-
-
-    // Get the plugin config manager that will be used for invoking plugins.
-    PluginConfigManager pluginConfigManager =
-         DirectoryServer.getPluginConfigManager();
-    boolean skipPostOperation = false;
-
-
-    // Create a labeled block of code that we can break out of if a problem is
-    // detected.
-bindProcessing:
-    {
-      // Invoke the pre-parse bind plugins.
-      PreParsePluginResult preParseResult =
-           pluginConfigManager.invokePreParseBindPlugins(this);
-      if (preParseResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the request and
-        // result and return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-
-        logBindRequest(this);
-        logBindResponse(this);
-        pluginConfigManager.invokePostResponseBindPlugins(this);
-        return;
-      }
-      else if (preParseResult.sendResponseImmediately())
-      {
-        skipPostOperation = true;
-        logBindRequest(this);
-        break bindProcessing;
-      }
-      else if (preParseResult.skipCoreProcessing())
-      {
-        skipPostOperation = false;
-        break bindProcessing;
-      }
-
-
-      // Log the bind request message.
-      logBindRequest(this);
-
-
-      // Process the bind DN to convert it from the raw form as provided by the
-      // client into the form required for the rest of the bind processing.
-      try
-      {
-        if (bindDN == null)
-        {
-          bindDN = DN.decode(rawBindDN);
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(ResultCode.INVALID_CREDENTIALS);
-        setAuthFailureReason(de.getMessageID(), de.getErrorMessage());
-        break bindProcessing;
-      }
-
-      // Check to see if the client has permission to perform the
-      // bind.
-
-      // FIXME: for now assume that this will check all permission
-      // pertinent to the operation. This includes any controls
-      // specified.
-      if (AccessControlConfigManager.getInstance()
-          .getAccessControlHandler().isAllowed(this) == false) {
-        setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        int    msgID   = MSGID_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-        String message = getMessage(msgID, String.valueOf(bindDN));
-        setAuthFailureReason(msgID, message);
-
-        skipPostOperation = true;
-        break bindProcessing;
-      }
-
-      // Check to see if there are any controls in the request.  If so, then see
-      // if there is any special processing required.
-      List<Control> requestControls = 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_AUTHZID_REQUEST))
-          {
-            returnAuthzID = true;
-          }
-          else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
-          {
-            pwPolicyControlRequested = true;
-          }
-
-          // NYI -- Add support for additional controls.
-          else if (c.isCritical())
-          {
-            setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
-
-            int msgID = MSGID_BIND_UNSUPPORTED_CRITICAL_CONTROL;
-            appendErrorMessage(getMessage(msgID, String.valueOf(oid)));
-
-            break bindProcessing;
-          }
-        }
-      }
-
-
-      // Check to see if this is a simple bind or a SASL bind and process
-      // accordingly.
-      switch (authType)
-      {
-        case SIMPLE:
-          // See if this is an anonymous bind.  If so, then determine whether
-          // to allow it.
-          if ((simplePassword == null) || (simplePassword.value().length == 0))
-          {
-            // If the server is in lockdown mode, then fail.
-            if (DirectoryServer.lockdownMode())
-            {
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-              int msgID = MSGID_BIND_REJECTED_LOCKDOWN_MODE;
-              setAuthFailureReason(msgID, getMessage(msgID));
-
-              processingStopTime = System.currentTimeMillis();
-              logBindResponse(this);
-              break bindProcessing;
-            }
-
-            // If there is a bind DN, then see whether that is acceptable.
-            if (DirectoryServer.bindWithDNRequiresPassword() &&
-                ((bindDN != null) && (! bindDN.isNullDN())))
-            {
-              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-              int    msgID   = MSGID_BIND_DN_BUT_NO_PASSWORD;
-              String message = getMessage(msgID);
-              setAuthFailureReason(msgID, message);
-              break bindProcessing;
-            }
-
-
-            // Invoke the pre-operation bind plugins.
-            PreOperationPluginResult preOpResult =
-                 pluginConfigManager.invokePreOperationBindPlugins(this);
-            if (preOpResult.connectionTerminated())
-            {
-              // There's no point in continuing with anything.  Log the result
-              // and return.
-              setResultCode(ResultCode.CANCELED);
-
-              int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-              appendErrorMessage(getMessage(msgID));
-
-              processingStopTime = System.currentTimeMillis();
-
-              logBindResponse(this);
-              pluginConfigManager.invokePostResponseBindPlugins(this);
-              return;
-            }
-            else if (preOpResult.sendResponseImmediately())
-            {
-              skipPostOperation = true;
-              break bindProcessing;
-            }
-            else if (preOpResult.skipCoreProcessing())
-            {
-              skipPostOperation = false;
-              break bindProcessing;
-            }
-
-            setResultCode(ResultCode.SUCCESS);
-            authInfo = new AuthenticationInfo();
-            break bindProcessing;
-          }
-
-
-          // See if the bind DN is actually one of the alternate root DNs
-          // defined in the server.  If so, then replace it with the actual DN
-          // for that user.
-          DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN);
-          if (actualRootDN != null)
-          {
-            bindDN = actualRootDN;
-          }
-
-
-          // Get the user entry based on the bind DN.  If it does not exist,
-          // then fail.
-          Lock userLock = null;
-          for (int i=0; i < 3; i++)
-          {
-            userLock = LockManager.lockRead(bindDN);
-            if (userLock != null)
-            {
-              break;
-            }
-          }
-
-          if (userLock == null)
-          {
-            int    msgID   = MSGID_BIND_OPERATION_CANNOT_LOCK_USER;
-            String message = getMessage(msgID, String.valueOf(bindDN));
-
-            setResultCode(DirectoryServer.getServerErrorResultCode());
-            setAuthFailureReason(msgID, message);
-            break bindProcessing;
-          }
-
-          try
-          {
-            Entry userEntry;
-            try
-            {
-              userEntry = DirectoryServer.getEntry(bindDN);
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(de.getMessageID(),
-                                   de.getErrorMessage());
-
-              userEntry = null;
-              break bindProcessing;
-            }
-
-            if (userEntry == null)
-            {
-
-              int    msgID   = MSGID_BIND_OPERATION_UNKNOWN_USER;
-              String message = getMessage(msgID, String.valueOf(bindDN));
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(msgID, message);
-              break bindProcessing;
-            }
-            else
-            {
-              userEntryDN = userEntry.getDN();
-            }
-
-
-            // Check to see if the user has a password.  If not, then fail.
-            // FIXME -- We need to have a way to enable/disable debugging.
-            pwPolicyState = new PasswordPolicyState(userEntry, false, false);
-            AttributeType pwType
-                 = pwPolicyState.getPolicy().getPasswordAttribute();
-
-            List<Attribute> pwAttr = userEntry.getAttribute(pwType);
-            if ((pwAttr == null) || (pwAttr.isEmpty()))
-            {
-              int    msgID   = MSGID_BIND_OPERATION_NO_PASSWORD;
-              String message = getMessage(msgID, String.valueOf(bindDN));
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(msgID, message);
-              break bindProcessing;
-            }
-
-
-            // Check to see if the authentication must be done in a secure
-            // manner.  If so, then the client connection must be secure.
-            if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
-                (! clientConnection.isSecure()))
-            {
-              int    msgID   = MSGID_BIND_OPERATION_INSECURE_SIMPLE_BIND;
-              String message = getMessage(msgID, String.valueOf(bindDN));
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(msgID, message);
-              break bindProcessing;
-            }
-
-
-            // Check to see if the user is administratively disabled or locked.
-            if (pwPolicyState.isDisabled())
-            {
-              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_DISABLED;
-              String message = getMessage(msgID, String.valueOf(bindDN));
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(msgID, message);
-              break bindProcessing;
-            }
-            else if (pwPolicyState.isAccountExpired())
-            {
-              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
-              String message = getMessage(msgID, String.valueOf(bindDN));
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(msgID, message);
-
-              pwPolicyState.generateAccountStatusNotification(
-                   AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID,
-                   message);
-
-              break bindProcessing;
-            }
-            else if (pwPolicyState.lockedDueToFailures())
-            {
-              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED;
-              String message = getMessage(msgID, String.valueOf(bindDN));
-
-              if (pwPolicyErrorType == null)
-              {
-                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
-              }
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(msgID, message);
-              break bindProcessing;
-            }
-            else if (pwPolicyState.lockedDueToMaximumResetAge())
-            {
-              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED;
-              String message = getMessage(msgID, String.valueOf(bindDN));
-
-              if (pwPolicyErrorType == null)
-              {
-                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
-              }
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(msgID, message);
-
-              pwPolicyState.generateAccountStatusNotification(
-                   AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN,
-                   msgID, message);
-
-              break bindProcessing;
-            }
-            else if (pwPolicyState.lockedDueToIdleInterval())
-            {
-              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED;
-              String message = getMessage(msgID, String.valueOf(bindDN));
-
-              if (pwPolicyErrorType == null)
-              {
-                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
-              }
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(msgID, message);
-
-              pwPolicyState.generateAccountStatusNotification(
-                   AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN,
-                   msgID, message);
-
-              break bindProcessing;
-            }
-
-
-            // Determine whether the password is expired, or whether the user
-            // should be warned about an upcoming expiration.
-            if (pwPolicyState.isPasswordExpired())
-            {
-              if (pwPolicyErrorType == null)
-              {
-                pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
-              }
-
-              int maxGraceLogins
-                   = pwPolicyState.getPolicy().getGraceLoginCount();
-              if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
-              {
-                List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes();
-                if ((graceLoginTimes == null) ||
-                    (graceLoginTimes.size() < maxGraceLogins))
-                {
-                  isGraceLogin       = true;
-                  mustChangePassword = true;
-
-                  if (pwPolicyWarningType == null)
-                  {
-                    pwPolicyWarningType =
-                         PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
-                    pwPolicyWarningValue = maxGraceLogins -
-                                           (graceLoginTimes.size() + 1);
-                  }
-                }
-                else
-                {
-                  int    msgID   = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
-                  String message = getMessage(msgID, String.valueOf(bindDN));
-
-                  setResultCode(ResultCode.INVALID_CREDENTIALS);
-                  setAuthFailureReason(msgID, message);
-
-                  pwPolicyState.generateAccountStatusNotification(
-                       AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
-                       msgID, message);
-
-                  break bindProcessing;
-                }
-              }
-              else
-              {
-                int    msgID   = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
-                String message = getMessage(msgID, String.valueOf(bindDN));
-
-                setResultCode(ResultCode.INVALID_CREDENTIALS);
-                setAuthFailureReason(msgID, message);
-
-                pwPolicyState.generateAccountStatusNotification(
-                     AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
-                     msgID, message);
-
-                break bindProcessing;
-              }
-            }
-            else if (pwPolicyState.shouldWarn())
-            {
-              int numSeconds = pwPolicyState.getSecondsUntilExpiration();
-              String timeToExpiration = secondsToTimeString(numSeconds);
-
-              int msgID = MSGID_BIND_PASSWORD_EXPIRING;
-              String message = getMessage(msgID, timeToExpiration);
-              appendErrorMessage(message);
-
-              if (pwPolicyWarningType == null)
-              {
-                pwPolicyWarningType =
-                     PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
-                pwPolicyWarningValue = numSeconds;
-              }
-
-              isFirstWarning = pwPolicyState.isFirstWarning();
-            }
-
-
-            // Check to see if the user's password has been reset.
-            if (pwPolicyState.mustChangePassword())
-            {
-              mustChangePassword = true;
-
-              if (pwPolicyErrorType == null)
-              {
-                pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
-              }
-            }
-
-
-            // Invoke the pre-operation bind plugins.
-            PreOperationPluginResult preOpResult =
-                 pluginConfigManager.invokePreOperationBindPlugins(this);
-            if (preOpResult.connectionTerminated())
-            {
-              // There's no point in continuing with anything.  Log the result
-              // and return.
-              setResultCode(ResultCode.CANCELED);
-
-              int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-              appendErrorMessage(getMessage(msgID));
-
-              processingStopTime = System.currentTimeMillis();
-
-              logBindResponse(this);
-              pluginConfigManager.invokePostResponseBindPlugins(this);
-              return;
-            }
-            else if (preOpResult.sendResponseImmediately())
-            {
-              skipPostOperation = true;
-              break bindProcessing;
-            }
-            else if (preOpResult.skipCoreProcessing())
-            {
-              skipPostOperation = false;
-              break bindProcessing;
-            }
-
-
-            // Determine whether the provided password matches any of the stored
-            // passwords for the user.
-            if (pwPolicyState.passwordMatches(simplePassword))
-            {
-              setResultCode(ResultCode.SUCCESS);
-
-              boolean isRoot = DirectoryServer.isRootDN(userEntry.getDN());
-              if (DirectoryServer.lockdownMode() && (! isRoot))
-              {
-                setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-                int msgID = MSGID_BIND_REJECTED_LOCKDOWN_MODE;
-                setAuthFailureReason(msgID, getMessage(msgID));
-
-                break bindProcessing;
-              }
-
-              authInfo = new AuthenticationInfo(userEntry, simplePassword,
-                                                isRoot);
-
-
-              // See if the user's entry contains a custom size limit.
-              AttributeType attrType =
-                   DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT,
-                                                 true);
-              List<Attribute> attrList = userEntry.getAttribute(attrType);
-              if ((attrList != null) && (attrList.size() == 1))
-              {
-                Attribute a = attrList.get(0);
-                LinkedHashSet<AttributeValue>  values = a.getValues();
-                Iterator<AttributeValue> iterator = values.iterator();
-                if (iterator.hasNext())
-                {
-                  AttributeValue v = iterator.next();
-                  if (iterator.hasNext())
-                  {
-                    int msgID = MSGID_BIND_MULTIPLE_USER_SIZE_LIMITS;
-                    String message =
-                         getMessage(msgID, String.valueOf(userEntry.getDN()));
-                    logError(ErrorLogCategory.CORE_SERVER,
-                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                  }
-                  else
-                  {
-                    try
-                    {
-                      sizeLimit = Integer.parseInt(v.getStringValue());
-                    }
-                    catch (Exception e)
-                    {
-                      if (debugEnabled())
-                      {
-                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                      }
-
-                      int msgID = MSGID_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT;
-                      String message =
-                           getMessage(msgID, v.getStringValue(),
-                                      String.valueOf(userEntry.getDN()));
-                      logError(ErrorLogCategory.CORE_SERVER,
-                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                    }
-                  }
-                }
-              }
-
-
-              // See if the user's entry contains a custom time limit.
-              attrType =
-                   DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT,
-                                                 true);
-              attrList = userEntry.getAttribute(attrType);
-              if ((attrList != null) && (attrList.size() == 1))
-              {
-                Attribute a = attrList.get(0);
-                LinkedHashSet<AttributeValue>  values = a.getValues();
-                Iterator<AttributeValue> iterator = values.iterator();
-                if (iterator.hasNext())
-                {
-                  AttributeValue v = iterator.next();
-                  if (iterator.hasNext())
-                  {
-                    int msgID = MSGID_BIND_MULTIPLE_USER_TIME_LIMITS;
-                    String message =
-                         getMessage(msgID, String.valueOf(userEntry.getDN()));
-                    logError(ErrorLogCategory.CORE_SERVER,
-                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                  }
-                  else
-                  {
-                    try
-                    {
-                      timeLimit = Integer.parseInt(v.getStringValue());
-                    }
-                    catch (Exception e)
-                    {
-                      if (debugEnabled())
-                      {
-                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                      }
-
-                      int msgID = MSGID_BIND_CANNOT_PROCESS_USER_TIME_LIMIT;
-                      String message =
-                           getMessage(msgID, v.getStringValue(),
-                                      String.valueOf(userEntry.getDN()));
-                      logError(ErrorLogCategory.CORE_SERVER,
-                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                    }
-                  }
-                }
-              }
-
-
-              // See if the user's entry contains a custom lookthrough limit.
-              attrType =
-                   DirectoryServer.getAttributeType(
-                       OP_ATTR_USER_LOOKTHROUGH_LIMIT, true);
-              attrList = userEntry.getAttribute(attrType);
-              if ((attrList != null) && (attrList.size() == 1))
-              {
-                Attribute a = attrList.get(0);
-                LinkedHashSet<AttributeValue>  values = a.getValues();
-                Iterator<AttributeValue> iterator = values.iterator();
-                if (iterator.hasNext())
-                {
-                  AttributeValue v = iterator.next();
-                  if (iterator.hasNext())
-                  {
-                    int msgID = MSGID_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS;
-                    String message =
-                         getMessage(msgID, String.valueOf(userEntry.getDN()));
-                    logError(ErrorLogCategory.CORE_SERVER,
-                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                  }
-                  else
-                  {
-                    try
-                    {
-                      lookthroughLimit = Integer.parseInt(v.getStringValue());
-                    }
-                    catch (Exception e)
-                    {
-                      if (debugEnabled())
-                      {
-                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                      }
-
-                      int msgID =
-                          MSGID_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT;
-                      String message =
-                           getMessage(msgID, v.getStringValue(),
-                                      String.valueOf(userEntry.getDN()));
-                      logError(ErrorLogCategory.CORE_SERVER,
-                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                    }
-                  }
-                }
-              }
-
-
-              pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
-              pwPolicyState.clearFailureLockout();
-
-              if (isFirstWarning)
-              {
-                pwPolicyState.setWarnedTime();
-
-                int numSeconds = pwPolicyState.getSecondsUntilExpiration();
-                String timeToExpiration = secondsToTimeString(numSeconds);
-
-                int msgID = MSGID_BIND_PASSWORD_EXPIRING;
-                String message = getMessage(msgID, timeToExpiration);
-
-                pwPolicyState.generateAccountStatusNotification(
-                     AccountStatusNotificationType.PASSWORD_EXPIRING, bindDN,
-                     msgID, message);
-              }
-
-              if (isGraceLogin)
-              {
-                pwPolicyState.updateGraceLoginTimes();
-              }
-
-              pwPolicyState.setLastLoginTime();
-            }
-            else
-            {
-              int    msgID   = MSGID_BIND_OPERATION_WRONG_PASSWORD;
-              String message = getMessage(msgID);
-
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-              setAuthFailureReason(msgID, message);
-
-              if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
-              {
-                pwPolicyState.updateAuthFailureTimes();
-                if (pwPolicyState.lockedDueToFailures())
-                {
-                  AccountStatusNotificationType notificationType;
-
-                  int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
-                  if (lockoutDuration > -1)
-                  {
-                    notificationType = AccountStatusNotificationType.
-                                            ACCOUNT_TEMPORARILY_LOCKED;
-                    msgID   = MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED;
-                    message = getMessage(msgID,
-                                         secondsToTimeString(lockoutDuration));
-                  }
-                  else
-                  {
-                    notificationType = AccountStatusNotificationType.
-                                            ACCOUNT_PERMANENTLY_LOCKED;
-                    msgID   = MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED;
-                    message = getMessage(msgID);
-                  }
-
-                  pwPolicyState.generateAccountStatusNotification(
-                       notificationType, userEntryDN, msgID, message);
-                }
-              }
-            }
-          }
-          catch (Exception e)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, e);
-            }
-
-            int    msgID   = MSGID_BIND_OPERATION_PASSWORD_VALIDATION_EXCEPTION;
-            String message = getMessage(msgID, getExceptionMessage(e));
-
-            setResultCode(DirectoryServer.getServerErrorResultCode());
-            setAuthFailureReason(msgID, message);
-            break bindProcessing;
-          }
-          finally
-          {
-            // No matter what, make sure to unlock the user's entry.
-            LockManager.unlock(bindDN, userLock);
-          }
-
-          break;
-
-
-        case SASL:
-          // Get the appropriate authentication handler for this request based
-          // on the SASL mechanism.  If there is none, then fail.
-          SASLMechanismHandler saslHandler =
-               DirectoryServer.getSASLMechanismHandler(saslMechanism);
-          if (saslHandler == null)
-          {
-            setResultCode(ResultCode.AUTH_METHOD_NOT_SUPPORTED);
-
-            int    msgID   = MSGID_BIND_OPERATION_UNKNOWN_SASL_MECHANISM;
-            String message = getMessage(msgID, saslMechanism);
-
-            appendErrorMessage(message);
-            setAuthFailureReason(msgID, message);
-            break bindProcessing;
-          }
-
-
-          // Check to see if the client has sufficient permission to perform the
-          // bind.
-          // NYI
-
-
-          // Invoke the pre-operation bind plugins.
-          PreOperationPluginResult preOpResult =
-               pluginConfigManager.invokePreOperationBindPlugins(this);
-          if (preOpResult.connectionTerminated())
-          {
-            // There's no point in continuing with anything.  Log the result
-            // and return.
-            setResultCode(ResultCode.CANCELED);
-
-            int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-            appendErrorMessage(getMessage(msgID));
-
-            processingStopTime = System.currentTimeMillis();
-
-            logBindResponse(this);
-            pluginConfigManager.invokePostResponseBindPlugins(this);
-            return;
-          }
-          else if (preOpResult.sendResponseImmediately())
-          {
-            skipPostOperation = true;
-            break bindProcessing;
-          }
-          else if (preOpResult.skipCoreProcessing())
-          {
-            skipPostOperation = false;
-            break bindProcessing;
-          }
-
-
-          // Actually process the SASL bind.
-          saslHandler.processSASLBind(this);
-
-
-          // If the server is operating in lockdown mode, then we will need to
-          // ensure that the authentication was successful and performed as a
-          // root user to continue.
-          if (DirectoryServer.lockdownMode())
-          {
-            ResultCode resultCode = getResultCode();
-            if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS)
-            {
-              if ((resultCode != ResultCode.SUCCESS) ||
-                  (saslAuthUserEntry == null) ||
-                  (! DirectoryServer.isRootDN(saslAuthUserEntry.getDN())))
-              {
-                setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-                int msgID = MSGID_BIND_REJECTED_LOCKDOWN_MODE;
-                setAuthFailureReason(msgID, getMessage(msgID));
-
-                break bindProcessing;
-              }
-            }
-          }
-
-
-          // Create the password policy state object.
-          String userDNString;
-          if (saslAuthUserEntry == null)
-          {
-            pwPolicyState = null;
-            userDNString  = null;
-          }
-          else
-          {
-            try
-            {
-              // FIXME -- Need to have a way to enable debugging.
-              pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false,
-                                                      false);
-              userEntryDN = saslAuthUserEntry.getDN();
-              userDNString = String.valueOf(userEntryDN);
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              setResponseData(de);
-              break bindProcessing;
-            }
-          }
-
-
-          // Perform password policy checks that will need to be completed
-          // regardless of whether the authentication was successful.
-          if (pwPolicyState != null)
-          {
-            if (pwPolicyState.isDisabled())
-            {
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_DISABLED;
-              String message = getMessage(msgID, userDNString);
-              setAuthFailureReason(msgID, message);
-              break bindProcessing;
-            }
-            else if (pwPolicyState.isAccountExpired())
-            {
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
-              String message = getMessage(msgID, userDNString);
-              setAuthFailureReason(msgID, message);
-
-              pwPolicyState.generateAccountStatusNotification(
-                   AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID,
-                   message);
-
-              break bindProcessing;
-            }
-
-            if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
-                (! clientConnection.isSecure()) &&
-                (! saslHandler.isSecure(saslMechanism)))
-            {
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-              int    msgID   = MSGID_BIND_OPERATION_INSECURE_SASL_BIND;
-              String message = getMessage(msgID, saslMechanism, userDNString);
-              setAuthFailureReason(msgID, message);
-              break bindProcessing;
-            }
-
-            if (pwPolicyState.lockedDueToFailures())
-            {
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-              if (pwPolicyErrorType == null)
-              {
-                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
-              }
-
-              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED;
-              String message = getMessage(msgID, userDNString);
-              setAuthFailureReason(msgID, message);
-              break bindProcessing;
-            }
-
-            if (pwPolicyState.lockedDueToIdleInterval())
-            {
-              setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-              if (pwPolicyErrorType == null)
-              {
-                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
-              }
-
-              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED;
-              String message = getMessage(msgID, userDNString);
-              setAuthFailureReason(msgID, message);
-
-              pwPolicyState.generateAccountStatusNotification(
-                   AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN,
-                   msgID, message);
-
-              break bindProcessing;
-            }
-
-
-            if (saslHandler.isPasswordBased(saslMechanism))
-            {
-              if (pwPolicyState.lockedDueToMaximumResetAge())
-              {
-                setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-                if (pwPolicyErrorType == null)
-                {
-                  pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
-                }
-
-                int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED;
-                String message = getMessage(msgID, userDNString);
-                setAuthFailureReason(msgID, message);
-
-                pwPolicyState.generateAccountStatusNotification(
-                     AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN,
-                     msgID, message);
-
-                break bindProcessing;
-              }
-
-              if (pwPolicyState.isPasswordExpired())
-              {
-                if (pwPolicyErrorType == null)
-                {
-                  pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
-                }
-
-                int maxGraceLogins
-                     = pwPolicyState.getPolicy().getGraceLoginCount();
-                if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
-                {
-                  List<Long> graceLoginTimes =
-                       pwPolicyState.getGraceLoginTimes();
-                  if ((graceLoginTimes == null) ||
-                      (graceLoginTimes.size() < maxGraceLogins))
-                  {
-                    isGraceLogin       = true;
-                    mustChangePassword = true;
-
-                    if (pwPolicyWarningType == null)
-                    {
-                      pwPolicyWarningType =
-                           PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
-                      pwPolicyWarningValue =
-                           maxGraceLogins - (graceLoginTimes.size() + 1);
-                    }
-                  }
-                  else
-                  {
-                    int    msgID   = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
-                    String message = getMessage(msgID, String.valueOf(bindDN));
-
-                    setResultCode(ResultCode.INVALID_CREDENTIALS);
-                    setAuthFailureReason(msgID, message);
-
-                    pwPolicyState.generateAccountStatusNotification(
-                         AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
-                         msgID, message);
-
-                    break bindProcessing;
-                  }
-                }
-                else
-                {
-                  int    msgID   = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
-                  String message = getMessage(msgID, String.valueOf(bindDN));
-
-                  setResultCode(ResultCode.INVALID_CREDENTIALS);
-                  setAuthFailureReason(msgID, message);
-
-                  pwPolicyState.generateAccountStatusNotification(
-                       AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
-                       msgID, message);
-
-                  break bindProcessing;
-                }
-              }
-              else if (pwPolicyState.shouldWarn())
-              {
-                int numSeconds = pwPolicyState.getSecondsUntilExpiration();
-                String timeToExpiration = secondsToTimeString(numSeconds);
-
-                int    msgID   = MSGID_BIND_PASSWORD_EXPIRING;
-                String message = getMessage(msgID, timeToExpiration);
-                appendErrorMessage(message);
-
-                if (pwPolicyWarningType == null)
-                {
-                  pwPolicyWarningType =
-                       PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
-                  pwPolicyWarningValue = numSeconds;
-                }
-
-                isFirstWarning = pwPolicyState.isFirstWarning();
-              }
-            }
-          }
-
-
-          // Determine whether the authentication was successful and perform
-          // any remaining password policy processing accordingly.  Also check
-          // for a custom size/time limit.
-          ResultCode resultCode = getResultCode();
-          if (resultCode == ResultCode.SUCCESS)
-          {
-            if (pwPolicyState != null)
-            {
-              if (saslHandler.isPasswordBased(saslMechanism) &&
-                  pwPolicyState.mustChangePassword())
-              {
-                mustChangePassword = true;
-              }
-
-              if (isFirstWarning)
-              {
-                pwPolicyState.setWarnedTime();
-
-                int numSeconds = pwPolicyState.getSecondsUntilExpiration();
-                String timeToExpiration = secondsToTimeString(numSeconds);
-
-                int msgID = MSGID_BIND_PASSWORD_EXPIRING;
-                String message = getMessage(msgID, timeToExpiration);
-
-                pwPolicyState.generateAccountStatusNotification(
-                     AccountStatusNotificationType.PASSWORD_EXPIRING, bindDN,
-                     msgID, message);
-              }
-
-              if (isGraceLogin)
-              {
-                pwPolicyState.updateGraceLoginTimes();
-              }
-
-              pwPolicyState.setLastLoginTime();
-
-
-              // See if the user's entry contains a custom size limit.
-              AttributeType attrType =
-                   DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT,
-                                                 true);
-              List<Attribute> attrList =
-                   saslAuthUserEntry.getAttribute(attrType);
-              if ((attrList != null) && (attrList.size() == 1))
-              {
-                Attribute a = attrList.get(0);
-                LinkedHashSet<AttributeValue>  values = a.getValues();
-                Iterator<AttributeValue> iterator = values.iterator();
-                if (iterator.hasNext())
-                {
-                  AttributeValue v = iterator.next();
-                  if (iterator.hasNext())
-                  {
-                    int msgID = MSGID_BIND_MULTIPLE_USER_SIZE_LIMITS;
-                    String message = getMessage(msgID, userDNString);
-                    logError(ErrorLogCategory.CORE_SERVER,
-                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                  }
-                  else
-                  {
-                    try
-                    {
-                      sizeLimit = Integer.parseInt(v.getStringValue());
-                    }
-                    catch (Exception e)
-                    {
-                      if (debugEnabled())
-                      {
-                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                      }
-
-                      int msgID = MSGID_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT;
-                      String message =
-                           getMessage(msgID, v.getStringValue(), userDNString);
-                      logError(ErrorLogCategory.CORE_SERVER,
-                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                    }
-                  }
-                }
-              }
-
-
-              // See if the user's entry contains a custom time limit.
-              attrType =
-                   DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT,
-                                                    true);
-              attrList = saslAuthUserEntry.getAttribute(attrType);
-              if ((attrList != null) && (attrList.size() == 1))
-              {
-                Attribute a = attrList.get(0);
-                LinkedHashSet<AttributeValue>  values = a.getValues();
-                Iterator<AttributeValue> iterator = values.iterator();
-                if (iterator.hasNext())
-                {
-                  AttributeValue v = iterator.next();
-                  if (iterator.hasNext())
-                  {
-                    int msgID = MSGID_BIND_MULTIPLE_USER_TIME_LIMITS;
-                    String message = getMessage(msgID, userDNString);
-                    logError(ErrorLogCategory.CORE_SERVER,
-                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                  }
-                  else
-                  {
-                    try
-                    {
-                      timeLimit = Integer.parseInt(v.getStringValue());
-                    }
-                    catch (Exception e)
-                    {
-                      if (debugEnabled())
-                      {
-                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                      }
-
-                      int msgID = MSGID_BIND_CANNOT_PROCESS_USER_TIME_LIMIT;
-                      String message =
-                           getMessage(msgID, v.getStringValue(), userDNString);
-                      logError(ErrorLogCategory.CORE_SERVER,
-                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                    }
-                  }
-                }
-              }
-
-
-              // See if the user's entry contains a custom lookthrough limit.
-              attrType =
-                   DirectoryServer.getAttributeType(
-                       OP_ATTR_USER_LOOKTHROUGH_LIMIT, true);
-              attrList = saslAuthUserEntry.getAttribute(attrType);
-              if ((attrList != null) && (attrList.size() == 1))
-              {
-                Attribute a = attrList.get(0);
-                LinkedHashSet<AttributeValue>  values = a.getValues();
-                Iterator<AttributeValue> iterator = values.iterator();
-                if (iterator.hasNext())
-                {
-                  AttributeValue v = iterator.next();
-                  if (iterator.hasNext())
-                  {
-                    int msgID = MSGID_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS;
-                    String message = getMessage(msgID, userDNString);
-                    logError(ErrorLogCategory.CORE_SERVER,
-                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                  }
-                  else
-                  {
-                    try
-                    {
-                      lookthroughLimit = Integer.parseInt(v.getStringValue());
-                    }
-                    catch (Exception e)
-                    {
-                      if (debugEnabled())
-                      {
-                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                      }
-
-                      int msgID =
-                          MSGID_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT;
-                      String message =
-                           getMessage(msgID, v.getStringValue(), userDNString);
-                      logError(ErrorLogCategory.CORE_SERVER,
-                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
-                    }
-                  }
-                }
-              }
-            }
-          }
-          else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
-          {
-            // FIXME -- Is any special processing needed here?
-          }
-          else
-          {
-            if (pwPolicyState != null)
-            {
-              if (saslHandler.isPasswordBased(saslMechanism))
-              {
-
-                if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
-                {
-                  pwPolicyState.updateAuthFailureTimes();
-                  if (pwPolicyState.lockedDueToFailures())
-                  {
-                    AccountStatusNotificationType notificationType;
-                    int msgID;
-                    String message;
-
-                    int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
-                    if (lockoutDuration > -1)
-                    {
-                      notificationType = AccountStatusNotificationType.
-                                              ACCOUNT_TEMPORARILY_LOCKED;
-                      msgID   = MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED;
-                      message = getMessage(msgID,
-                                     secondsToTimeString(lockoutDuration));
-                    }
-                    else
-                    {
-                      notificationType = AccountStatusNotificationType.
-                                              ACCOUNT_PERMANENTLY_LOCKED;
-                      msgID   = MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED;
-                      message = getMessage(msgID);
-                    }
-
-                    pwPolicyState.generateAccountStatusNotification(
-                         notificationType, userEntryDN, msgID, message);
-                  }
-                }
-              }
-            }
-          }
-
-          break;
-
-
-        default:
-          // Send a protocol error response to the client and disconnect.
-          // NYI
-          return;
-      }
-    }
-
-
-    // Update the user's account with any password policy changes that may be
-    // required.
-    try
-    {
-      if (pwPolicyState != null)
-      {
-        pwPolicyState.updateUserEntry();
-      }
-    }
-    catch (DirectoryException de)
-    {
-      if (debugEnabled())
-      {
-        TRACER.debugCaught(DebugLogLevel.ERROR, de);
-      }
-
-      setResponseData(de);
-    }
-
-
-    // Invoke the post-operation bind plugins.
-    if (! skipPostOperation)
-    {
-      PostOperationPluginResult postOpResult =
-           pluginConfigManager.invokePostOperationBindPlugins(this);
-      if (postOpResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the result
-        // and return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-
-        logBindResponse(this);
-        pluginConfigManager.invokePostResponseBindPlugins(this);
-        return;
-      }
-    }
-
-
-    // Update the authentication information for the user.
-    if ((getResultCode() == ResultCode.SUCCESS) && (authInfo != null))
-    {
-      authenticatedUserEntry = authInfo.getAuthenticationEntry();
-      clientConnection.setAuthenticationInfo(authInfo);
-      clientConnection.setSizeLimit(sizeLimit);
-      clientConnection.setTimeLimit(timeLimit);
-      clientConnection.setLookthroughLimit(lookthroughLimit);
-      clientConnection.setMustChangePassword(mustChangePassword);
-
-      if (returnAuthzID)
-      {
-        responseControls.add(new AuthorizationIdentityResponseControl(
-                                      authInfo.getAuthorizationDN()));
-      }
-    }
-
-
-    // See if we need to send a password policy control to the client.  If so,
-    // then add it to the response.
-    if (getResultCode() == ResultCode.SUCCESS)
-    {
-      if (pwPolicyControlRequested)
-      {
-        PasswordPolicyResponseControl pwpControl =
-             new PasswordPolicyResponseControl(pwPolicyWarningType,
-                                               pwPolicyWarningValue,
-                                               pwPolicyErrorType);
-        responseControls.add(pwpControl);
-      }
-      else
-      {
-        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
-        {
-          responseControls.add(new PasswordExpiredControl());
-        }
-        else if (pwPolicyWarningType ==
-                 PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION)
-        {
-          responseControls.add(new PasswordExpiringControl(
-                                        pwPolicyWarningValue));
-        }
-      }
-    }
-    else
-    {
-      if (pwPolicyControlRequested)
-      {
-        PasswordPolicyResponseControl pwpControl =
-             new PasswordPolicyResponseControl(pwPolicyWarningType,
-                                               pwPolicyWarningValue,
-                                               pwPolicyErrorType);
-        responseControls.add(pwpControl);
-      }
-      else
-      {
-        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
-        {
-          responseControls.add(new PasswordExpiredControl());
-        }
-      }
-    }
-
-
-    // Unset the "bind in progress" flag to allow other operations to be
-    // processed.
-    // FIXME -- Make sure this also gets unset at every possible point at which
-    // the bind could fail and this method could return early.
-    clientConnection.setBindInProgress(false);
-
-
-    // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
-
-
-    // Send the bind response to the client.
-    clientConnection.sendResponse(this);
-
-
-    // Log the bind response.
-    logBindResponse(this);
-
-
-    // Invoke the post-response bind plugins.
-    pluginConfigManager.invokePostResponseBindPlugins(this);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelResult cancel(CancelRequest cancelRequest)
-  {
-    cancelRequest.addResponseMessage(getMessage(MSGID_CANNOT_CANCEL_BIND));
-    return CancelResult.CANNOT_CANCEL;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelRequest getCancelRequest()
-  {
-    return null;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
-  {
-    // Bind operations cannot be canceled.
-    return false;
-  }
-
+  public abstract void setUserEntryDN(DN userEntryDN);
 
 
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void toString(StringBuilder buffer)
-  {
-    buffer.append("BindOperation(connID=");
-    buffer.append(clientConnection.getConnectionID());
-    buffer.append(", opID=");
-    buffer.append(operationID);
-    buffer.append(", protocol=\"");
-    buffer.append(clientConnection.getProtocol());
-    buffer.append(" ");
-    buffer.append(protocolVersion);
-    buffer.append("\", dn=");
-    buffer.append(rawBindDN);
-    buffer.append(", authType=");
-    buffer.append(authType);
-    buffer.append(")");
-  }
 }
-
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/BindOperationBasis.java b/opendj-sdk/opends/src/server/org/opends/server/core/BindOperationBasis.java
new file mode 100644
index 0000000..541d04a
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/BindOperationBasis.java
@@ -0,0 +1,959 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_AUTH_TYPE;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_BIND_DN;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ERROR_MESSAGE;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_MATCHED_DN;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_PROCESSING_TIME;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_REFERRAL_URLS;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_SASL_MECHANISM;
+import static org.opends.server.loggers.AccessLogger.logBindRequest;
+import static org.opends.server.loggers.AccessLogger.logBindResponse;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.messages.CoreMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.plugin.PreParsePluginResult;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.AbstractOperation;
+import org.opends.server.types.AuthenticationInfo;
+import org.opends.server.types.AuthenticationType;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Operation;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.operation.PreParseBindOperation;
+import org.opends.server.workflowelement.localbackend.*;
+
+
+
+
+/**
+ * This class defines an operation that may be used to authenticate a user to
+ * the Directory Server.  Note that for security restrictions, response messages
+ * that may be returned to the client must be carefully cleaned to ensure that
+ * they do not provide a malicious client with information that may be useful in
+ * an attack.  This does impact the debugability of the server, but that can
+ * be addressed by calling the <CODE>setAuthFailureReason</CODE> method, which
+ * can provide a reason for a failure in a form that will not be returned to the
+ * client but may be written to a log file.
+ */
+public class BindOperationBasis
+             extends AbstractOperation
+             implements BindOperation, PreParseBindOperation
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
+
+  // The credentials used for SASL authentication.
+  private ASN1OctetString saslCredentials;
+
+  // The server SASL credentials provided to the client in the response.
+  private ASN1OctetString serverSASLCredentials;
+
+  // The authentication info for this bind operation.
+  private AuthenticationInfo authInfo = null;
+
+  // The authentication type used for this bind operation.
+  private AuthenticationType authType;
+
+  // The raw, unprocessed bind DN as contained in the client request.
+  private ByteString rawBindDN;
+
+  // The password used for simple authentication.
+  private ByteString simplePassword;
+
+  // The bind DN used for this bind operation.
+  private DN bindDN;
+
+  // The DN of the user entry that is attempting to authenticate.
+  private DN userEntryDN;
+
+  // The DN of the user as whom a SASL authentication was attempted (regardless
+  // of whether the authentication was successful) for the purpose of updating
+  // password policy state information.
+  private Entry saslAuthUserEntry;
+
+  // The unique ID associated with the failure reason message.
+  private int authFailureID;
+
+  // The set of response controls for this bind operation.
+  private List<Control> responseControls;
+
+  // A message explaining the reason for the authentication failure.
+  private String authFailureReason;
+
+  // The SASL mechanism used for SASL authentication.
+  private String saslMechanism;
+
+  // A string representation of the protocol version for this bind operation.
+  private String protocolVersion;
+
+  /**
+   * Creates a new simple bind operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  protocolVersion   The string representation of the protocol version
+   *                           associated with this bind request.
+   * @param  rawBindDN         The raw, unprocessed bind DN as provided in the
+   *                           request from the client.
+   * @param  simplePassword    The password to use for the simple
+   *                           authentication.
+   */
+  public BindOperationBasis(ClientConnection clientConnection, long operationID,
+                       int messageID, List<Control> requestControls,
+                       String protocolVersion, ByteString rawBindDN,
+                       ByteString simplePassword)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.protocolVersion = protocolVersion;
+    this.authType        = AuthenticationType.SIMPLE;
+    this.saslMechanism   = null;
+    this.saslCredentials = null;
+
+    if (rawBindDN == null)
+    {
+      this.rawBindDN = new ASN1OctetString();
+    }
+    else
+    {
+      this.rawBindDN = rawBindDN;
+    }
+
+    if (simplePassword == null)
+    {
+      this.simplePassword = new ASN1OctetString();
+    }
+    else
+    {
+      this.simplePassword = simplePassword;
+    }
+
+    bindDN                   = null;
+    userEntryDN              = null;
+    responseControls         = new ArrayList<Control>(0);
+    authFailureID            = 0;
+    authFailureReason        = null;
+    saslAuthUserEntry        = null;
+  }
+
+
+
+  /**
+   * Creates a new SASL bind operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  protocolVersion   The string representation of the protocol version
+   *                           associated with this bind request.
+   * @param  rawBindDN         The raw, unprocessed bind DN as provided in the
+   *                           request from the client.
+   * @param  saslMechanism     The SASL mechanism included in the request.
+   * @param  saslCredentials   The optional SASL credentials included in the
+   *                           request.
+   */
+  public BindOperationBasis(ClientConnection clientConnection, long operationID,
+                       int messageID, List<Control> requestControls,
+                       String protocolVersion, ByteString rawBindDN,
+                       String saslMechanism, ASN1OctetString saslCredentials)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.protocolVersion = protocolVersion;
+    this.authType        = AuthenticationType.SASL;
+    this.saslMechanism   = saslMechanism;
+    this.saslCredentials = saslCredentials;
+    this.simplePassword  = null;
+
+    if (rawBindDN == null)
+    {
+      this.rawBindDN = new ASN1OctetString();
+    }
+    else
+    {
+      this.rawBindDN = rawBindDN;
+    }
+
+    bindDN                 = null;
+    userEntryDN            = null;
+    responseControls       = new ArrayList<Control>(0);
+    authFailureID          = 0;
+    authFailureReason      = null;
+    saslAuthUserEntry      = null;
+  }
+
+
+
+  /**
+   * Creates a new simple bind operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  protocolVersion   The string representation of the protocol version
+   *                           associated with this bind request.
+   * @param  bindDN            The bind DN for this bind operation.
+   * @param  simplePassword    The password to use for the simple
+   *                           authentication.
+   */
+  public BindOperationBasis(ClientConnection clientConnection, long operationID,
+                       int messageID, List<Control> requestControls,
+                       String protocolVersion, DN bindDN,
+                       ByteString simplePassword)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.protocolVersion = protocolVersion;
+    this.authType        = AuthenticationType.SIMPLE;
+    this.bindDN          = bindDN;
+    this.saslMechanism   = null;
+    this.saslCredentials = null;
+
+    if (bindDN == null)
+    {
+      rawBindDN = new ASN1OctetString();
+    }
+    else
+    {
+      rawBindDN = new ASN1OctetString(bindDN.toString());
+    }
+
+    if (simplePassword == null)
+    {
+      this.simplePassword = new ASN1OctetString();
+    }
+    else
+    {
+      this.simplePassword = simplePassword;
+    }
+
+    responseControls         = new ArrayList<Control>(0);
+    authFailureID            = 0;
+    authFailureReason        = null;
+    saslAuthUserEntry        = null;
+    userEntryDN              = null;
+  }
+
+
+
+  /**
+   * Creates a new SASL bind operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  protocolVersion   The string representation of the protocol version
+   *                           associated with this bind request.
+   * @param  bindDN            The bind DN for this bind operation.
+   * @param  saslMechanism     The SASL mechanism included in the request.
+   * @param  saslCredentials   The optional SASL credentials included in the
+   *                           request.
+   */
+  public BindOperationBasis(ClientConnection clientConnection, long operationID,
+                       int messageID, List<Control> requestControls,
+                       String protocolVersion, DN bindDN,
+                       String saslMechanism, ASN1OctetString saslCredentials)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.protocolVersion = protocolVersion;
+    this.authType        = AuthenticationType.SASL;
+    this.bindDN          = bindDN;
+    this.saslMechanism   = saslMechanism;
+    this.saslCredentials = saslCredentials;
+    this.simplePassword  = null;
+
+    if (bindDN == null)
+    {
+      rawBindDN = new ASN1OctetString();
+    }
+    else
+    {
+      rawBindDN = new ASN1OctetString(bindDN.toString());
+    }
+
+    responseControls       = new ArrayList<Control>(0);
+    authFailureID          = 0;
+    authFailureReason      = null;
+    saslAuthUserEntry      = null;
+    userEntryDN            = null;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final AuthenticationType getAuthenticationType()
+  {
+    return authType;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ByteString getRawBindDN()
+  {
+    return rawBindDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawBindDN(ByteString rawBindDN)
+  {
+    if (rawBindDN == null)
+    {
+      this.rawBindDN = new ASN1OctetString();
+    }
+    else
+    {
+      this.rawBindDN = rawBindDN;
+    }
+
+    bindDN = null;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DN getBindDN()
+  {
+    try
+    {
+      if (bindDN == null)
+      {
+        bindDN = DN.decode(rawBindDN);
+      }
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      setResultCode(ResultCode.INVALID_CREDENTIALS);
+      setAuthFailureReason(de.getMessageID(), de.getErrorMessage());
+    }
+    return bindDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ByteString getSimplePassword()
+  {
+    return simplePassword;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setSimplePassword(ByteString simplePassword)
+  {
+    if (simplePassword == null)
+    {
+      this.simplePassword = new ASN1OctetString();
+    }
+    else
+    {
+      this.simplePassword = simplePassword;
+    }
+
+    authType        = AuthenticationType.SIMPLE;
+    saslMechanism   = null;
+    saslCredentials = null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String getSASLMechanism()
+  {
+    return  saslMechanism;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ASN1OctetString getSASLCredentials()
+  {
+    return saslCredentials;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setSASLCredentials(String saslMechanism,
+                                       ASN1OctetString saslCredentials)
+  {
+    this.saslMechanism   = saslMechanism;
+    this.saslCredentials = saslCredentials;
+
+    authType       = AuthenticationType.SASL;
+    simplePassword = null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ASN1OctetString getServerSASLCredentials()
+  {
+    return serverSASLCredentials;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setServerSASLCredentials(ASN1OctetString
+                                                  serverSASLCredentials)
+  {
+    this.serverSASLCredentials = serverSASLCredentials;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Entry getSASLAuthUserEntry()
+  {
+    return saslAuthUserEntry;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setSASLAuthUserEntry(Entry saslAuthUserEntry)
+  {
+    this.saslAuthUserEntry = saslAuthUserEntry;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String getAuthFailureReason()
+  {
+    return authFailureReason;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final int getAuthFailureID()
+  {
+    return authFailureID;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setAuthFailureReason(int id, String reason)
+  {
+    if (id < 0)
+    {
+      authFailureID = 0;
+    }
+    else
+    {
+      authFailureID = id;
+    }
+
+    authFailureReason = reason;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DN getUserEntryDN()
+  {
+    return userEntryDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final AuthenticationInfo getAuthenticationInfo()
+  {
+    return authInfo;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setAuthenticationInfo(AuthenticationInfo authInfo)
+  {
+    this.authInfo = authInfo;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final OperationType getOperationType()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    return OperationType.BIND;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void disconnectClient(DisconnectReason disconnectReason,
+                                     boolean sendNotification, String message,
+                                     int messageID)
+  {
+    // Since bind operations can't be cancelled, we don't need to do anything
+    // but forward the request on to the client connection.
+    clientConnection.disconnect(disconnectReason, sendNotification, message,
+                                messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final String[][] getRequestLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    if (authType == AuthenticationType.SASL)
+    {
+      return new String[][]
+      {
+        new String[] { LOG_ELEMENT_BIND_DN, String.valueOf(rawBindDN) },
+        new String[] { LOG_ELEMENT_AUTH_TYPE, authType.toString() },
+        new String[] { LOG_ELEMENT_SASL_MECHANISM, saslMechanism }
+      };
+    }
+    else
+    {
+      return new String[][]
+      {
+        new String[] { LOG_ELEMENT_BIND_DN, String.valueOf(rawBindDN) },
+        new String[] { LOG_ELEMENT_AUTH_TYPE, authType.toString() }
+      };
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final String[][] getResponseLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    String resultCode = String.valueOf(getResultCode().getIntValue());
+
+    String errorMessage;
+    StringBuilder errorMessageBuffer = getErrorMessage();
+    if (errorMessageBuffer == null)
+    {
+      errorMessage = null;
+    }
+    else
+    {
+      errorMessage = errorMessageBuffer.toString();
+    }
+
+    String matchedDNStr;
+    DN matchedDN = getMatchedDN();
+    if (matchedDN == null)
+    {
+      matchedDNStr = null;
+    }
+    else
+    {
+      matchedDNStr = matchedDN.toString();
+    }
+
+    String referrals;
+    List<String> referralURLs = getReferralURLs();
+    if ((referralURLs == null) || referralURLs.isEmpty())
+    {
+      referrals = null;
+    }
+    else
+    {
+      StringBuilder buffer = new StringBuilder();
+      Iterator<String> iterator = referralURLs.iterator();
+      buffer.append(iterator.next());
+
+      while (iterator.hasNext())
+      {
+        buffer.append(", ");
+        buffer.append(iterator.next());
+      }
+
+      referrals = buffer.toString();
+    }
+
+    String processingTime =
+         String.valueOf(getProcessingTime());
+
+    return new String[][]
+    {
+      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
+      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
+      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
+      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
+      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
+    };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final List<Control> getResponseControls()
+  {
+    return responseControls;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void addResponseControl(Control control)
+  {
+    responseControls.add(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void removeResponseControl(Control control)
+  {
+    responseControls.remove(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final CancelResult cancel(CancelRequest cancelRequest)
+  {
+    cancelRequest.addResponseMessage(getMessage(MSGID_CANNOT_CANCEL_BIND));
+    return CancelResult.CANNOT_CANCEL;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final CancelRequest getCancelRequest()
+  {
+    return null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public
+  boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    // Bind operations cannot be canceled.
+    return false;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void toString(StringBuilder buffer)
+  {
+    buffer.append("BindOperation(connID=");
+    buffer.append(clientConnection.getConnectionID());
+    buffer.append(", opID=");
+    buffer.append(operationID);
+    buffer.append(", protocol=\"");
+    buffer.append(clientConnection.getProtocol());
+    buffer.append(" ");
+    buffer.append(protocolVersion);
+    buffer.append(", dn=");
+    buffer.append(rawBindDN);
+    buffer.append(", authType=");
+    buffer.append(authType);
+    buffer.append(")");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setUserEntryDN(DN userEntryDN)
+  {
+    this.userEntryDN = userEntryDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getProtocolVersion()
+  {
+    return protocolVersion;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProtocolVersion(String protocolVersion)
+  {
+    this.protocolVersion = protocolVersion;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void run()
+  {
+    // Start the processing timer and initially set the result to indicate that
+    // the result is unknown.
+    setProcessingStartTime();
+    ClientConnection clientConnection = getClientConnection();
+
+    setResultCode(ResultCode.UNDEFINED);
+
+    // Set a flag to indicate that a bind operation is in progress.  This should
+    // ensure that no new operations will be accepted for this client until the
+    // bind is complete.
+    clientConnection.setBindInProgress(true);
+
+    // Wipe out any existing authentication for the client connection and create
+    // a placeholder that will be used if the bind is successful.
+    clientConnection.setUnauthenticated();
+
+    // Abandon any operations that may be in progress for the client.
+    String cancelReason = getMessage(MSGID_CANCELED_BY_BIND_REQUEST);
+    CancelRequest cancelRequest = new CancelRequest(true, cancelReason);
+    clientConnection.cancelAllOperationsExcept(cancelRequest, getMessageID());
+
+
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+      DirectoryServer.getPluginConfigManager();
+
+
+    // Create a labeled block of code that we can break out of if a problem is
+    // detected.
+    bindProcessing:
+    {
+      // Invoke the pre-parse bind plugins.
+      PreParsePluginResult preParseResult =
+        pluginConfigManager.invokePreParseBindPlugins(this);
+      if (preParseResult.connectionTerminated())
+      {
+        // There's no point in continuing with anything.  Log the request and
+        // result and return.
+        setResultCode(ResultCode.CANCELED);
+
+        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
+        appendErrorMessage(getMessage(msgID));
+
+        setProcessingStopTime();
+
+        logBindRequest(this);
+        logBindResponse(this);
+        return;
+      }
+      else if (preParseResult.sendResponseImmediately())
+      {
+        logBindRequest(this);
+        break bindProcessing;
+      }
+      else if (preParseResult.skipCoreProcessing())
+      {
+        break bindProcessing;
+      }
+
+      // Log the bind request message.
+      logBindRequest(this);
+
+      // Process the bind DN to convert it from the raw form as provided by the
+      // client into the form required for the rest of the bind processing.
+      DN bindDN = getBindDN();
+      if (bindDN == null){
+        break bindProcessing;
+      }
+
+      // If this is a simple bind
+      // Then check wether the bind DN is actually one of the alternate root DNs
+      // defined in the server.  If so, then replace it with the actual DN
+      // for that user.
+      switch (getAuthenticationType())
+      {
+      case SIMPLE:
+        DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN);
+        if (actualRootDN != null)
+        {
+          bindDN = actualRootDN;
+        }
+      }
+
+
+      // Retrieve the network group attached to the client connection
+      // and get a workflow to process the operation.
+      NetworkGroup ng = getClientConnection().getNetworkGroup();
+      Workflow workflow = ng.getWorkflowCandidate(bindDN);
+      if (workflow == null)
+      {
+        // We have found no workflow for the requested base DN, just return
+        // a no such entry result code and stop the processing.
+        updateOperationErrMsgAndResCode();
+        break bindProcessing;
+      }
+      workflow.execute(this);
+    }
+
+    // Check for and handle a request to cancel this operation.
+    if (getCancelResult() == CancelResult.CANCELED)
+    {
+      setProcessingStopTime();
+      logBindResponse(this);
+      invokePostResponsePlugins();
+      return;
+    }
+
+    // Unset the "bind in progress" flag to allow other operations to be
+    // processed.
+    // FIXME -- Make sure this also gets unset at every possible point at which
+    // the bind could fail and this method could return early.
+    clientConnection.setBindInProgress(false);
+
+    // Stop the processing timer.
+    setProcessingStopTime();
+
+    // Send the bind response to the client.
+    clientConnection.sendResponse(this);
+
+    // Log the bind response.
+    logBindResponse(this);
+
+    // Check wether there are local operations in attachments
+    List localOperations =
+      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
+    if (localOperations != null && (! localOperations.isEmpty())){
+      for (Object localOp : localOperations)
+      {
+        LocalBackendBindOperation localOperation =
+          (LocalBackendBindOperation)localOp;
+        // Invoke the post-response bind plugins.
+        pluginConfigManager.invokePostResponseBindPlugins(localOperation);
+      }
+    }
+  }
+
+  /**
+   * Updates the error message and the result code of the operation.
+   *
+   * This method is called because no workflows were found to process
+   * the operation.
+   */
+  private void updateOperationErrMsgAndResCode()
+  {
+    int    msgID   = MSGID_BIND_OPERATION_UNKNOWN_USER;
+    String message = getMessage(msgID, String.valueOf(getBindDN()));
+
+    setResultCode(ResultCode.INVALID_CREDENTIALS);
+    setAuthFailureReason(msgID, message);
+  }
+
+  /**
+   * Execute the postResponseBindPlugins.
+   */
+  private void invokePostResponsePlugins()
+  {
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+      DirectoryServer.getPluginConfigManager();
+
+    // Check wether there are local operations in attachments
+    List localOperations =
+      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
+
+    if (localOperations != null && (! localOperations.isEmpty())){
+      for (Object localOp : localOperations)
+      {
+        LocalBackendBindOperation localOperation =
+          (LocalBackendBindOperation)localOp;
+        // Invoke the post-response bind plugins.
+        pluginConfigManager.invokePostResponseBindPlugins(localOperation);
+      }
+    }
+  }
+
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/BindOperationWrapper.java b/opendj-sdk/opends/src/server/org/opends/server/core/BindOperationWrapper.java
new file mode 100644
index 0000000..ddb784c
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/BindOperationWrapper.java
@@ -0,0 +1,691 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.AuthenticationInfo;
+import org.opends.server.types.AuthenticationType;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.ResultCode;
+
+/**
+ * This abstract class wraps/decorates a given bind operation.
+ * This class will be extended by sub-classes to enhance the
+ * functionnality of the BindOperationBasis.
+ */
+public abstract class BindOperationWrapper implements BindOperation
+{
+  private BindOperation bind;
+
+  /**
+   * Creates a new bind operation based on the provided bind operation.
+   *
+   * @param bind The bind operation to wrap
+   */
+  protected BindOperationWrapper(BindOperation bind){
+    this.bind = bind;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addRequestControl(Control control)
+  {
+    bind.addRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addResponseControl(Control control)
+  {
+    bind.addResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendAdditionalLogMessage(String message)
+  {
+    bind.appendAdditionalLogMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendErrorMessage(String message)
+  {
+    bind.appendErrorMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult cancel(CancelRequest cancelRequest)
+  {
+    return bind.cancel(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void disconnectClient(DisconnectReason disconnectReason,
+      boolean sendNotification, String message, int messageID)
+  {
+    bind.disconnectClient(disconnectReason, sendNotification, message,
+        messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean dontSynchronize()
+  {
+    return bind.dontSynchronize();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getAdditionalLogMessage()
+  {
+    return bind.getAdditionalLogMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object getAttachment(String name)
+  {
+    return bind.getAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<String, Object> getAttachments()
+  {
+    return bind.getAttachments();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public AuthenticationInfo getAuthenticationInfo()
+  {
+    return bind.getAuthenticationInfo();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public AuthenticationType getAuthenticationType()
+  {
+    return bind.getAuthenticationType();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getAuthFailureID()
+  {
+    return bind.getAuthFailureID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getAuthFailureReason()
+  {
+    return bind.getAuthFailureReason();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getAuthorizationDN()
+  {
+    return bind.getAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry getAuthorizationEntry()
+  {
+    return bind.getAuthorizationEntry();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getBindDN()
+  {
+    return bind.getBindDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelRequest getCancelRequest()
+  {
+    return bind.getCancelRequest();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult getCancelResult()
+  {
+    return bind.getCancelResult();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ClientConnection getClientConnection()
+  {
+    return bind.getClientConnection();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getCommonLogElements()
+  {
+    return bind.getCommonLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getConnectionID()
+  {
+    return bind.getConnectionID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getErrorMessage()
+  {
+    return bind.getErrorMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getMatchedDN()
+  {
+    return bind.getMatchedDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getMessageID()
+  {
+    return bind.getMessageID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getOperationID()
+  {
+    return bind.getOperationID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public OperationType getOperationType()
+  {
+    return bind.getOperationType();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStartTime()
+  {
+    return bind.getProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStopTime()
+  {
+    return bind.getProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingTime()
+  {
+    return bind.getProcessingTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getRawBindDN()
+  {
+    return bind.getRawBindDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<String> getReferralURLs()
+  {
+    return bind.getReferralURLs();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getRequestControls()
+  {
+    return bind.getRequestControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getRequestLogElements()
+  {
+    return bind.getRequestLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getResponseControls()
+  {
+    return bind.getResponseControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getResponseLogElements()
+  {
+    return bind.getResponseLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ResultCode getResultCode()
+  {
+    return bind.getResultCode();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry getSASLAuthUserEntry()
+  {
+    return bind.getSASLAuthUserEntry();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1OctetString getSASLCredentials()
+  {
+    return bind.getSASLCredentials();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getSASLMechanism()
+  {
+    return bind.getSASLMechanism();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1OctetString getServerSASLCredentials()
+  {
+    return bind.getServerSASLCredentials();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getSimplePassword()
+  {
+    return bind.getSimplePassword();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getUserEntryDN()
+  {
+    return bind.getUserEntryDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void indicateCancelled(CancelRequest cancelRequest)
+  {
+    bind.indicateCancelled(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isInternalOperation()
+  {
+    return bind.isInternalOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isSynchronizationOperation()
+  {
+    return bind.isSynchronizationOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void operationCompleted()
+  {
+    bind.operationCompleted();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object removeAttachment(String name)
+  {
+    return bind.removeAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeRequestControl(Control control)
+  {
+    bind.removeRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeResponseControl(Control control)
+  {
+    bind.removeResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAdditionalLogMessage(StringBuilder additionalLogMessage)
+  {
+    bind.setAdditionalLogMessage(additionalLogMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object setAttachment(String name, Object value)
+  {
+    return bind.setAttachment(name, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttachments(Map<String, Object> attachments)
+  {
+    bind.setAttachments(attachments);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAuthenticationInfo(AuthenticationInfo authInfo)
+  {
+    bind.setAuthenticationInfo(authInfo);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAuthFailureReason(int id, String reason)
+  {
+    bind.setAuthFailureReason(id, reason);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAuthorizationEntry(Entry authorizationEntry)
+  {
+    bind.setAuthorizationEntry(authorizationEntry);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    return bind.setCancelRequest(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setCancelResult(CancelResult cancelResult)
+  {
+    bind.setCancelResult(cancelResult);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setDontSynchronize(boolean dontSynchronize)
+  {
+    bind.setDontSynchronize(dontSynchronize);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setErrorMessage(StringBuilder errorMessage)
+  {
+    bind.setErrorMessage(errorMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setInternalOperation(boolean isInternalOperation)
+  {
+    bind.setInternalOperation(isInternalOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setMatchedDN(DN matchedDN)
+  {
+    bind.setMatchedDN(matchedDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStartTime()
+  {
+    bind.setProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStopTime()
+  {
+    bind.setProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawBindDN(ByteString rawBindDN)
+  {
+    bind.setRawBindDN(rawBindDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setReferralURLs(List<String> referralURLs)
+  {
+    bind.setReferralURLs(referralURLs);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResponseData(DirectoryException directoryException)
+  {
+    bind.setResponseData(directoryException);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResultCode(ResultCode resultCode)
+  {
+    bind.setResultCode(resultCode);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSASLAuthUserEntry(Entry saslAuthUserEntry)
+  {
+    bind.setSASLAuthUserEntry(saslAuthUserEntry);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSASLCredentials(String saslMechanism,
+      ASN1OctetString saslCredentials)
+  {
+    bind.setSASLCredentials(saslMechanism, saslCredentials);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setServerSASLCredentials(ASN1OctetString serverSASLCredentials)
+  {
+    bind.setServerSASLCredentials(serverSASLCredentials);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSimplePassword(ByteString simplePassword)
+  {
+    bind.setSimplePassword(simplePassword);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSynchronizationOperation(boolean isSynchronizationOperation)
+  {
+    bind.setSynchronizationOperation(isSynchronizationOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setUserEntryDN(DN userEntryDN){
+    bind.setUserEntryDN(userEntryDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return bind.toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void toString(StringBuilder buffer)
+  {
+    bind.toString(buffer);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProtocolVersion(String protocolVersion)
+  {
+    bind.setProtocolVersion(protocolVersion);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getProtocolVersion()
+  {
+    return bind.getProtocolVersion();
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java
index 30bf972..1a3af75 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/CompareOperation.java
@@ -43,6 +43,7 @@
 import org.opends.server.controls.ProxiedAuthV1Control;
 import org.opends.server.controls.ProxiedAuthV2Control;
 import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValue;
@@ -56,7 +57,6 @@
 import org.opends.server.types.Entry;
 import org.opends.server.types.LDAPException;
 import org.opends.server.types.LockManager;
-import org.opends.server.types.Operation;
 import org.opends.server.types.OperationType;
 import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
@@ -69,6 +69,8 @@
 import static org.opends.server.core.CoreConstants.*;
 import static org.opends.server.loggers.AccessLogger.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
+
+import org.opends.server.loggers.debug.DebugLogger;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.types.DebugLogLevel;
 import static org.opends.server.messages.CoreMessages.*;
@@ -84,14 +86,14 @@
  * pair.
  */
 public class CompareOperation
-       extends Operation
+       extends AbstractOperation
        implements PreParseCompareOperation, PreOperationCompareOperation,
                   PostOperationCompareOperation, PostResponseCompareOperation
 {
   /**
    * The tracer object for the debug logger.
    */
-  private static final DebugTracer TRACER = getTracer();
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
 
   // The attribute type for this compare operation.
   private AttributeType attributeType;
@@ -117,12 +119,6 @@
   // The set of response controls for this compare operation.
   private List<Control> responseControls;
 
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
   // The attribute type for the compare operation.
   private String rawAttributeType;
 
@@ -324,41 +320,6 @@
     return entry;
   }
 
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
-
   /**
    * {@inheritDoc}
    */
@@ -465,7 +426,7 @@
     }
 
     String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
+         String.valueOf(getProcessingTime());
 
     return new String[][]
     {
@@ -528,9 +489,12 @@
 
 
   /**
-   * {@inheritDoc}
+   * Performs the work of actually processing this operation.  This
+   * should include all processing for the operation, including
+   * invoking plugins, logging messages, performing access control,
+   * managing synchronization, and any other work that might need to
+   * be done in the course of processing.
    */
-  @Override()
   public final void run()
   {
     setResultCode(ResultCode.UNDEFINED);
@@ -543,14 +507,14 @@
 
 
     // Start the processing timer.
-    processingStartTime = System.currentTimeMillis();
+    setProcessingStartTime();
 
 
     // Check for and handle a request to cancel this operation.
     if (cancelRequest != null)
     {
       indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
+      setProcessingStopTime();
       return;
     }
 
@@ -571,7 +535,7 @@
         int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
         appendErrorMessage(getMessage(msgID));
 
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
 
         logCompareRequest(this);
         logCompareResponse(this);
@@ -599,7 +563,7 @@
       if (cancelRequest != null)
       {
         indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
         logCompareResponse(this);
         pluginConfigManager.invokePostResponseComparePlugins(this);
         return;
@@ -648,7 +612,7 @@
       if (cancelRequest != null)
       {
         indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
         logCompareResponse(this);
         pluginConfigManager.invokePostResponseComparePlugins(this);
         return;
@@ -998,7 +962,7 @@
         if (cancelRequest != null)
         {
           indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
+          setProcessingStopTime();
           logCompareResponse(this);
           pluginConfigManager.invokePostResponseComparePlugins(this);
           return;
@@ -1017,7 +981,7 @@
           int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
           appendErrorMessage(getMessage(msgID));
 
-          processingStopTime = System.currentTimeMillis();
+          setProcessingStopTime();
           logCompareResponse(this);
           pluginConfigManager.invokePostResponseComparePlugins(this);
           return;
@@ -1127,7 +1091,7 @@
     if (cancelRequest != null)
     {
       indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
+      setProcessingStopTime();
       logCompareResponse(this);
       pluginConfigManager.invokePostResponseComparePlugins(this);
       return;
@@ -1146,7 +1110,7 @@
         int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
         appendErrorMessage(getMessage(msgID));
 
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
         logCompareResponse(this);
         pluginConfigManager.invokePostResponseComparePlugins(this);
         return;
@@ -1159,7 +1123,7 @@
 
 
     // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
+    setProcessingStopTime();
 
 
     // Send the compare response to the client.
@@ -1233,7 +1197,7 @@
    * {@inheritDoc}
    */
   @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
+  public boolean setCancelRequest(CancelRequest cancelRequest)
   {
     this.cancelRequest = cancelRequest;
     return true;
@@ -1257,5 +1221,6 @@
     buffer.append(rawAttributeType);
     buffer.append(")");
   }
+
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java b/opendj-sdk/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java
index 6800688..0c9867e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java
@@ -30,11 +30,8 @@
 import org.opends.server.api.AccessControlHandler;
 import org.opends.server.api.AccessControlProvider;
 import org.opends.server.config.ConfigException;
-import org.opends.server.types.InitializationException;
-import org.opends.server.types.Operation;
-import org.opends.server.types.SearchResultEntry;
-import org.opends.server.types.SearchResultReference;
 import org.opends.server.types.*;
+import org.opends.server.workflowelement.localbackend.*;
 
 /**
  * This class implements a default access control provider for the
@@ -103,7 +100,7 @@
      * {@inheritDoc}
      */
     @Override
-    public boolean isAllowed(AddOperation addOperation) {
+    public boolean isAllowed(LocalBackendAddOperation addOperation) {
 
       return true;
     }
@@ -112,7 +109,7 @@
      * {@inheritDoc}
      */
     @Override
-    public boolean isAllowed(BindOperation bindOperation) {
+    public boolean isAllowed(LocalBackendBindOperation bindOperation) {
 
       return true;
     }
@@ -130,7 +127,7 @@
      * {@inheritDoc}
      */
     @Override
-    public boolean isAllowed(DeleteOperation deleteOperation) {
+    public boolean isAllowed(LocalBackendDeleteOperation deleteOperation) {
 
       return true;
     }
@@ -148,7 +145,7 @@
      * {@inheritDoc}
      */
     @Override
-    public boolean isAllowed(ModifyOperation modifyOperation) {
+    public boolean isAllowed(LocalBackendModifyOperation modifyOperation) {
 
       return true;
     }
@@ -166,7 +163,7 @@
      * {@inheritDoc}
      */
     @Override
-    public boolean isAllowed(SearchOperation searchOperation) {
+    public boolean isAllowed(LocalBackendSearchOperation searchOperation) {
 
       return true;
     }
@@ -186,7 +183,8 @@
      */
     @Override
     public SearchResultEntry filterEntry(
-        SearchOperation searchOperation, SearchResultEntry searchEntry) {
+        SearchOperation searchOperation,
+        SearchResultEntry searchEntry) {
 
       // No implementation required.
       return searchEntry;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperation.java
index c28f696..56aa4d4 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperation.java
@@ -26,169 +26,16 @@
  */
 package org.opends.server.core;
 
-
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.locks.Lock;
-
-import org.opends.server.api.Backend;
-import org.opends.server.api.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.api.plugin.PreParsePluginResult;
-import org.opends.server.controls.LDAPAssertionRequestControl;
-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.protocols.asn1.ASN1OctetString;
-import org.opends.server.types.AttributeType;
 import org.opends.server.types.ByteString;
-import org.opends.server.types.CancelledOperationException;
-import org.opends.server.types.CancelRequest;
-import org.opends.server.types.CancelResult;
-import org.opends.server.types.Control;
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.DN;
-import org.opends.server.types.Entry;
-import org.opends.server.types.ErrorLogCategory;
-import org.opends.server.types.ErrorLogSeverity;
-import org.opends.server.types.LDAPException;
-import org.opends.server.types.LockManager;
 import org.opends.server.types.Operation;
-import org.opends.server.types.OperationType;
-import org.opends.server.types.Privilege;
-import org.opends.server.types.ResultCode;
-import org.opends.server.types.SearchFilter;
-import org.opends.server.types.SearchResultEntry;
-import org.opends.server.types.SynchronizationProviderResult;
-import org.opends.server.types.operation.PostOperationDeleteOperation;
-import org.opends.server.types.operation.PostResponseDeleteOperation;
-import org.opends.server.types.operation.PreOperationDeleteOperation;
-import org.opends.server.types.operation.PreParseDeleteOperation;
-
-import static org.opends.server.core.CoreConstants.*;
-import static org.opends.server.loggers.AccessLogger.*;
-import org.opends.server.types.DebugLogLevel;
-import static org.opends.server.loggers.ErrorLogger.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
-import static org.opends.server.messages.CoreMessages.*;
-import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
-
 
 /**
- * This class defines an operation that may be used to remove an entry from the
- * Directory Server.
+ * This interface defines an operation that may be used to remove an entry from
+ * the Directory Server.
  */
-public class DeleteOperation
-       extends Operation
-       implements PreParseDeleteOperation, PreOperationDeleteOperation,
-                  PostOperationDeleteOperation, PostResponseDeleteOperation
+public interface DeleteOperation extends Operation
 {
-  /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = getTracer();
-
-
-
-
-  // The raw, unprocessed entry DN as included in the client request.
-  private ByteString rawEntryDN;
-
-  // The cancel request that has been issued for this delete operation.
-  private CancelRequest cancelRequest;
-
-  // The DN of the entry for the delete operation.
-  private DN entryDN;
-
-  // The proxied authorization target DN for this operation.
-  private DN proxiedAuthorizationDN;
-
-  // The entry to be deleted.
-  private Entry entry;
-
-  // The set of response controls for this delete operation.
-  private List<Control> responseControls;
-
-  // The change number that has been assigned to this operation.
-  private long changeNumber;
-
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
-
-
-  /**
-   * Creates a new delete operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  rawEntryDN        The raw, unprocessed DN of the entry to delete,
-   *                           as included in the client request.
-   */
-  public DeleteOperation(ClientConnection clientConnection, long operationID,
-                         int messageID, List<Control> requestControls,
-                         ByteString rawEntryDN)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.rawEntryDN = rawEntryDN;
-
-    entry            = null;
-    entryDN          = null;
-    responseControls = new ArrayList<Control>();
-    cancelRequest    = null;
-    changeNumber     = -1;
-  }
-
-
-
-  /**
-   * Creates a new delete operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  entryDN           The entry DN for this delete operation.
-   */
-  public DeleteOperation(ClientConnection clientConnection, long operationID,
-                         int messageID, List<Control> requestControls,
-                         DN entryDN)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.entryDN = entryDN;
-
-    rawEntryDN       = new ASN1OctetString(entryDN.toString());
-    responseControls = new ArrayList<Control>();
-    cancelRequest    = null;
-    changeNumber     = -1;
-    entry            = null;
-  }
-
-
 
   /**
    * Retrieves the raw, unprocessed entry DN as included in the client request.
@@ -197,12 +44,7 @@
    *
    * @return  The raw, unprocessed entry DN as included in the client request.
    */
-  public final ByteString getRawEntryDN()
-  {
-    return rawEntryDN;
-  }
-
-
+  public abstract ByteString getRawEntryDN();
 
   /**
    * Specifies the raw, unprocessed entry DN as included in the client request.
@@ -212,14 +54,7 @@
    * @param  rawEntryDN  The raw, unprocessed entry DN as included in the client
    *                     request.
    */
-  public final void setRawEntryDN(ByteString rawEntryDN)
-  {
-    this.rawEntryDN = rawEntryDN;
-
-    entryDN = null;
-  }
-
-
+  public abstract void setRawEntryDN(ByteString rawEntryDN);
 
   /**
    * Retrieves the DN of the entry to delete.  This should not be called by
@@ -229,59 +64,7 @@
    * @return  The DN of the entry to delete, or <CODE>null</CODE> if the raw
    *          entry DN has not yet been processed.
    */
-  public final DN getEntryDN()
-  {
-    return entryDN;
-  }
-
-
-
-  /**
-   * Retrieves the entry to be deleted.  This will not be available to pre-parse
-   * plugins.
-   *
-   * @return  The entry to be deleted, or <CODE>null</CODE> if the entry is not
-   *          yet available.
-   */
-  public final Entry getEntryToDelete()
-  {
-    return entry;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
+  public abstract DN getEntryDN();
 
   /**
    * Retrieves the change number that has been assigned to this operation.
@@ -290,12 +73,7 @@
    *          if none has been assigned yet or if there is no applicable
    *          synchronization mechanism in place that uses change numbers.
    */
-  public final long getChangeNumber()
-  {
-    return changeNumber;
-  }
-
-
+  public abstract long getChangeNumber();
 
   /**
    * Specifies the change number that has been assigned to this operation by the
@@ -304,131 +82,7 @@
    * @param  changeNumber  The change number that has been assigned to this
    *                       operation by the synchronization mechanism.
    */
-  public final void setChangeNumber(long changeNumber)
-  {
-    this.changeNumber = changeNumber;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final OperationType getOperationType()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    return OperationType.DELETE;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void disconnectClient(DisconnectReason disconnectReason,
-                                     boolean sendNotification, String message,
-                                     int messageID)
-  {
-    // Before calling clientConnection.disconnect, we need to mark this
-    // operation as cancelled so that the attempt to cancel it later won't cause
-    // an unnecessary delay.
-    setCancelResult(CancelResult.CANCELED);
-
-    clientConnection.disconnect(disconnectReason, sendNotification, message,
-                                messageID);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final String[][] getRequestLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) }
-    };
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final String[][] getResponseLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    String resultCode = String.valueOf(getResultCode().getIntValue());
-
-    String errorMessage;
-    StringBuilder errorMessageBuffer = getErrorMessage();
-    if (errorMessageBuffer == null)
-    {
-      errorMessage = null;
-    }
-    else
-    {
-      errorMessage = errorMessageBuffer.toString();
-    }
-
-    String matchedDNStr;
-    DN matchedDN = getMatchedDN();
-    if (matchedDN == null)
-    {
-      matchedDNStr = null;
-    }
-    else
-    {
-      matchedDNStr = matchedDN.toString();
-    }
-
-    String referrals;
-    List<String> referralURLs = getReferralURLs();
-    if ((referralURLs == null) || referralURLs.isEmpty())
-    {
-      referrals = null;
-    }
-    else
-    {
-      StringBuilder buffer = new StringBuilder();
-      Iterator<String> iterator = referralURLs.iterator();
-      buffer.append(iterator.next());
-
-      while (iterator.hasNext())
-      {
-        buffer.append(", ");
-        buffer.append(iterator.next());
-      }
-
-      referrals = buffer.toString();
-    }
-
-    String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
-      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
-      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
-      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
-      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
-    };
-  }
-
-
+  public abstract void setChangeNumber(long changeNumber);
 
   /**
    * Retrieves the proxied authorization DN for this operation if proxied
@@ -438,1021 +92,18 @@
    *          authorization has been requested, or {@code null} if proxied
    *          authorization has not been requested.
    */
-  public DN getProxiedAuthorizationDN()
-  {
-    return proxiedAuthorizationDN;
-  }
-
-
+  public abstract DN getProxiedAuthorizationDN();
 
   /**
-   * {@inheritDoc}
+   * Set the proxied authorization DN for this operation if proxied
+   * authorization has been requested.
+   *
+   * @param proxiedAuthorizationDN
+   *          The proxied authorization DN for this operation if proxied
+   *          authorization has been requested, or {@code null} if proxied
+   *          authorization has not been requested.
    */
-  @Override()
-  public final List<Control> getResponseControls()
-  {
-    return responseControls;
-  }
+  public abstract void setProxiedAuthorizationDN(DN proxiedAuthorizationDN);
 
 
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void addResponseControl(Control control)
-  {
-    responseControls.add(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void removeResponseControl(Control control)
-  {
-    responseControls.remove(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void run()
-  {
-    setResultCode(ResultCode.UNDEFINED);
-
-
-    // Get the plugin config manager that will be used for invoking plugins.
-    PluginConfigManager pluginConfigManager =
-         DirectoryServer.getPluginConfigManager();
-    boolean skipPostOperation = false;
-
-
-    // Start the processing timer.
-    processingStartTime = System.currentTimeMillis();
-
-
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
-      return;
-    }
-
-
-    // Create a labeled block of code that we can break out of if a problem is
-    // detected.
-deleteProcessing:
-    {
-      // Invoke the pre-parse delete plugins.
-      PreParsePluginResult preParseResult =
-           pluginConfigManager.invokePreParseDeletePlugins(this);
-      if (preParseResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the request and
-        // result and return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-
-        logDeleteRequest(this);
-        logDeleteResponse(this);
-        pluginConfigManager.invokePostResponseDeletePlugins(this);
-        return;
-      }
-      else if (preParseResult.sendResponseImmediately())
-      {
-        skipPostOperation = true;
-        logDeleteRequest(this);
-        break deleteProcessing;
-      }
-      else if (preParseResult.skipCoreProcessing())
-      {
-        skipPostOperation = false;
-        break deleteProcessing;
-      }
-
-
-      // Log the delete request message.
-      logDeleteRequest(this);
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logDeleteResponse(this);
-        pluginConfigManager.invokePostResponseDeletePlugins(this);
-        return;
-      }
-
-
-      // Process the entry DN to convert it from its raw form as provided by the
-      // client to the form required for the rest of the delete processing.
-      try
-      {
-        if (entryDN == null)
-        {
-          entryDN = DN.decode(rawEntryDN);
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        setMatchedDN(de.getMatchedDN());
-        setReferralURLs(de.getReferralURLs());
-
-        break deleteProcessing;
-      }
-
-
-      // Grab a write lock on the entry.
-      Lock entryLock = null;
-      for (int i=0; i < 3; i++)
-      {
-        entryLock = LockManager.lockWrite(entryDN);
-        if (entryLock != null)
-        {
-          break;
-        }
-      }
-
-      if (entryLock == null)
-      {
-        setResultCode(DirectoryServer.getServerErrorResultCode());
-        appendErrorMessage(getMessage(MSGID_DELETE_CANNOT_LOCK_ENTRY,
-                                      String.valueOf(entryDN)));
-        break deleteProcessing;
-      }
-
-
-      try
-      {
-        // Get the entry to delete.  If it doesn't exist, then fail.
-        try
-        {
-          entry = DirectoryServer.getEntry(entryDN);
-          if (entry == null)
-          {
-            setResultCode(ResultCode.NO_SUCH_OBJECT);
-            appendErrorMessage(getMessage(MSGID_DELETE_NO_SUCH_ENTRY,
-                                          String.valueOf(entryDN)));
-
-            try
-            {
-              DN parentDN = entryDN.getParentDNInSuffix();
-              while (parentDN != null)
-              {
-                if (DirectoryServer.entryExists(parentDN))
-                {
-                  setMatchedDN(parentDN);
-                  break;
-                }
-
-                parentDN = parentDN.getParentDNInSuffix();
-              }
-            }
-            catch (Exception e)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, e);
-              }
-            }
-
-            break deleteProcessing;
-          }
-        }
-        catch (DirectoryException de)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
-          }
-
-          setResultCode(de.getResultCode());
-          appendErrorMessage(de.getErrorMessage());
-          setMatchedDN(de.getMatchedDN());
-          setReferralURLs(de.getReferralURLs());
-          break deleteProcessing;
-        }
-
-
-        // Invoke any conflict resolution processing that might be needed by the
-        // synchronization provider.
-        for (SynchronizationProvider provider :
-             DirectoryServer.getSynchronizationProviders())
-        {
-          try
-          {
-            SynchronizationProviderResult result =
-                 provider.handleConflictResolution(this);
-            if (! result.continueOperationProcessing())
-            {
-              break deleteProcessing;
-            }
-          }
-          catch (DirectoryException de)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, de);
-            }
-
-            logError(ErrorLogCategory.SYNCHRONIZATION,
-                     ErrorLogSeverity.SEVERE_ERROR,
-                     MSGID_DELETE_SYNCH_CONFLICT_RESOLUTION_FAILED,
-                     getConnectionID(), getOperationID(),
-                     getExceptionMessage(de));
-
-            setResponseData(de);
-            break deleteProcessing;
-          }
-        }
-
-        // Check to see if the client has permission to perform the
-        // delete.
-
-        // Check to see if there are any controls in the request.  If so, then
-        // see if there is any special processing required.
-        boolean                   noOp           = false;
-        LDAPPreReadRequestControl preReadRequest = null;
-        List<Control> requestControls = getRequestControls();
-        if ((requestControls != null) && (! requestControls.isEmpty()))
-        {
-          for (int i=0; i < requestControls.size(); i++)
-          {
-            Control c   = requestControls.get(i);
-            String  oid = c.getOID();
-
-            if (oid.equals(OID_LDAP_ASSERTION))
-            {
-              LDAPAssertionRequestControl assertControl;
-              if (c instanceof LDAPAssertionRequestControl)
-              {
-                assertControl = (LDAPAssertionRequestControl) c;
-              }
-              else
-              {
-                try
-                {
-                  assertControl = LDAPAssertionRequestControl.decodeControl(c);
-                  requestControls.set(i, assertControl);
-                }
-                catch (LDAPException le)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break deleteProcessing;
-                }
-              }
-
-              try
-              {
-                // FIXME -- We need to determine whether the current user has
-                //          permission to make this determination.
-                SearchFilter filter = assertControl.getSearchFilter();
-                if (! filter.matchesEntry(entry))
-                {
-                  setResultCode(ResultCode.ASSERTION_FAILED);
-
-                  appendErrorMessage(getMessage(MSGID_DELETE_ASSERTION_FAILED,
-                                                String.valueOf(entryDN)));
-
-                  break deleteProcessing;
-                }
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(ResultCode.PROTOCOL_ERROR);
-
-                int msgID = MSGID_DELETE_CANNOT_PROCESS_ASSERTION_FILTER;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              de.getErrorMessage()));
-
-                break deleteProcessing;
-              }
-            }
-            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);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break deleteProcessing;
-                }
-              }
-            }
-            else if (oid.equals(OID_PROXIED_AUTH_V1))
-            {
-              // The requester must have the PROXIED_AUTH privilige in order to
-              // be able to use this control.
-              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-              {
-                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-                appendErrorMessage(getMessage(msgID));
-                setResultCode(ResultCode.AUTHORIZATION_DENIED);
-                break deleteProcessing;
-              }
-
-
-              ProxiedAuthV1Control proxyControl;
-              if (c instanceof ProxiedAuthV1Control)
-              {
-                proxyControl = (ProxiedAuthV1Control) c;
-              }
-              else
-              {
-                try
-                {
-                  proxyControl = ProxiedAuthV1Control.decodeControl(c);
-                }
-                catch (LDAPException le)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break deleteProcessing;
-                }
-              }
-
-
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
-
-                break deleteProcessing;
-              }
-
-              if (AccessControlConfigManager.getInstance()
-                      .getAccessControlHandler().isProxiedAuthAllowed(this,
-                      authorizationEntry) == false) {
-                setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-                int msgID = MSGID_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-                skipPostOperation = true;
-                break deleteProcessing;
-              }
-              setAuthorizationEntry(authorizationEntry);
-              if (authorizationEntry == null)
-              {
-                proxiedAuthorizationDN = DN.nullDN();
-              }
-              else
-              {
-                proxiedAuthorizationDN = authorizationEntry.getDN();
-              }
-            }
-            else if (oid.equals(OID_PROXIED_AUTH_V2))
-            {
-              // The requester must have the PROXIED_AUTH privilige in order to
-              // be able to use this control.
-              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-              {
-                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-                appendErrorMessage(getMessage(msgID));
-                setResultCode(ResultCode.AUTHORIZATION_DENIED);
-                break deleteProcessing;
-              }
-
-
-              ProxiedAuthV2Control proxyControl;
-              if (c instanceof ProxiedAuthV2Control)
-              {
-                proxyControl = (ProxiedAuthV2Control) c;
-              }
-              else
-              {
-                try
-                {
-                  proxyControl = ProxiedAuthV2Control.decodeControl(c);
-                }
-                catch (LDAPException le)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break deleteProcessing;
-                }
-              }
-
-
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
-
-                break deleteProcessing;
-              }
-              if (AccessControlConfigManager.getInstance()
-                  .getAccessControlHandler().isProxiedAuthAllowed(this,
-                                                authorizationEntry) == false) {
-                setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-                int msgID = MSGID_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-                skipPostOperation = true;
-                break deleteProcessing;
-              }
-
-              setAuthorizationEntry(authorizationEntry);
-              if (authorizationEntry == null)
-              {
-                proxiedAuthorizationDN = DN.nullDN();
-              }
-              else
-              {
-                proxiedAuthorizationDN = authorizationEntry.getDN();
-              }
-            }
-
-            // NYI -- Add support for additional controls.
-            else if (c.isCritical())
-            {
-              Backend backend = DirectoryServer.getBackend(entryDN);
-              if ((backend == null) || (! backend.supportsControl(oid)))
-              {
-                setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
-
-                int msgID = MSGID_DELETE_UNSUPPORTED_CRITICAL_CONTROL;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              oid));
-
-                break deleteProcessing;
-              }
-            }
-          }
-        }
-
-
-        // FIXME: for now assume that this will check all permission
-        // pertinent to the operation. This includes proxy authorization
-        // and any other controls specified.
-
-        // FIXME: earlier checks to see if the entry already exists may
-        // have already exposed sensitive information to the client.
-        if (AccessControlConfigManager.getInstance()
-            .getAccessControlHandler().isAllowed(this) == false) {
-          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-          int msgID = MSGID_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-          appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-          skipPostOperation = true;
-          break deleteProcessing;
-        }
-
-        // Check for and handle a request to cancel this operation.
-        if (cancelRequest != null)
-        {
-          indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
-          logDeleteResponse(this);
-          pluginConfigManager.invokePostResponseDeletePlugins(this);
-          return;
-        }
-
-
-        // If the operation is not a synchronization operation,
-        // invoke the pre-delete plugins.
-        if (!isSynchronizationOperation())
-        {
-          PreOperationPluginResult preOpResult =
-            pluginConfigManager.invokePreOperationDeletePlugins(this);
-          if (preOpResult.connectionTerminated())
-          {
-            // There's no point in continuing with anything.  Log the request
-            // and result and return.
-            setResultCode(ResultCode.CANCELED);
-
-            int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-            appendErrorMessage(getMessage(msgID));
-
-            processingStopTime = System.currentTimeMillis();
-            logDeleteResponse(this);
-            pluginConfigManager.invokePostResponseDeletePlugins(this);
-            return;
-          }
-          else if (preOpResult.sendResponseImmediately())
-          {
-            skipPostOperation = true;
-            break deleteProcessing;
-          }
-          else if (preOpResult.skipCoreProcessing())
-          {
-            skipPostOperation = false;
-            break deleteProcessing;
-          }
-        }
-
-
-        // Check for and handle a request to cancel this operation.
-        if (cancelRequest != null)
-        {
-          indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
-          logDeleteResponse(this);
-          pluginConfigManager.invokePostResponseDeletePlugins(this);
-          return;
-        }
-
-
-        // Get the backend to use for the delete.  If there is none, then fail.
-        Backend backend = DirectoryServer.getBackend(entryDN);
-        if (backend == null)
-        {
-          setResultCode(ResultCode.NO_SUCH_OBJECT);
-          appendErrorMessage(getMessage(MSGID_DELETE_NO_SUCH_ENTRY,
-                                        String.valueOf(entryDN)));
-          break deleteProcessing;
-        }
-
-
-        // If it is not a private backend, then check to see if the server or
-        // backend is operating in read-only mode.
-        if (! backend.isPrivateBackend())
-        {
-          switch (DirectoryServer.getWritabilityMode())
-          {
-            case DISABLED:
-              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-              appendErrorMessage(getMessage(MSGID_DELETE_SERVER_READONLY,
-                                            String.valueOf(entryDN)));
-              break deleteProcessing;
-
-            case INTERNAL_ONLY:
-              if (! (isInternalOperation() || isSynchronizationOperation()))
-              {
-                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                appendErrorMessage(getMessage(MSGID_DELETE_SERVER_READONLY,
-                                              String.valueOf(entryDN)));
-                break deleteProcessing;
-              }
-          }
-
-          switch (backend.getWritabilityMode())
-          {
-            case DISABLED:
-              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-              appendErrorMessage(getMessage(MSGID_DELETE_BACKEND_READONLY,
-                                            String.valueOf(entryDN)));
-              break deleteProcessing;
-
-            case INTERNAL_ONLY:
-              if (! (isInternalOperation() || isSynchronizationOperation()))
-              {
-                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                appendErrorMessage(getMessage(MSGID_DELETE_BACKEND_READONLY,
-                                              String.valueOf(entryDN)));
-                break deleteProcessing;
-              }
-          }
-        }
-
-
-        // The selected backend will have the responsibility of making sure that
-        // the entry actually exists and does not have any children (or possibly
-        // handling a subtree delete).  But we will need to check if there are
-        // any subordinate backends that should stop us from attempting the
-        // delete.
-        Backend[] subBackends = backend.getSubordinateBackends();
-        for (Backend b : subBackends)
-        {
-          DN[] baseDNs = b.getBaseDNs();
-          for (DN dn : baseDNs)
-          {
-            if (dn.isDescendantOf(entryDN))
-            {
-              setResultCode(ResultCode.NOT_ALLOWED_ON_NONLEAF);
-              appendErrorMessage(getMessage(MSGID_DELETE_HAS_SUB_BACKEND,
-                                            String.valueOf(entryDN),
-                                            String.valueOf(dn)));
-              break deleteProcessing;
-            }
-          }
-        }
-
-
-        // Actually perform the delete.
-        try
-        {
-          if (noOp)
-          {
-            appendErrorMessage(getMessage(MSGID_DELETE_NOOP));
-
-            // FIXME -- We must set a result code other than SUCCESS.
-          }
-          else
-          {
-            for (SynchronizationProvider provider :
-                 DirectoryServer.getSynchronizationProviders())
-            {
-              try
-              {
-                SynchronizationProviderResult result =
-                     provider.doPreOperation(this);
-                if (! result.continueOperationProcessing())
-                {
-                  break deleteProcessing;
-                }
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                logError(ErrorLogCategory.SYNCHRONIZATION,
-                         ErrorLogSeverity.SEVERE_ERROR,
-                         MSGID_DELETE_SYNCH_PREOP_FAILED, getConnectionID(),
-                         getOperationID(), getExceptionMessage(de));
-
-                setResponseData(de);
-                break deleteProcessing;
-              }
-            }
-
-            backend.deleteEntry(entryDN, this);
-          }
-
-          if (preReadRequest != null)
-          {
-            Entry entryCopy = entry.duplicate(true);
-
-            if (! preReadRequest.allowsAttribute(
-                       DirectoryServer.getObjectClassAttributeType()))
-            {
-              entryCopy.removeAttribute(
-                   DirectoryServer.getObjectClassAttributeType());
-            }
-
-            if (! preReadRequest.returnAllUserAttributes())
-            {
-              Iterator<AttributeType> iterator =
-                   entryCopy.getUserAttributes().keySet().iterator();
-              while (iterator.hasNext())
-              {
-                AttributeType attrType = iterator.next();
-                if (! preReadRequest.allowsAttribute(attrType))
-                {
-                  iterator.remove();
-                }
-              }
-            }
-
-            if (! preReadRequest.returnAllOperationalAttributes())
-            {
-              Iterator<AttributeType> iterator =
-                   entryCopy.getOperationalAttributes().keySet().iterator();
-              while (iterator.hasNext())
-              {
-                AttributeType attrType = iterator.next();
-                if (! preReadRequest.allowsAttribute(attrType))
-                {
-                  iterator.remove();
-                }
-              }
-            }
-
-            // FIXME -- Check access controls on the entry to see if it should
-            //          be returned or if any attributes need to be stripped
-            //          out..
-            SearchResultEntry searchEntry = new SearchResultEntry(entryCopy);
-            LDAPPreReadResponseControl responseControl =
-                 new LDAPPreReadResponseControl(preReadRequest.getOID(),
-                                                preReadRequest.isCritical(),
-                                                searchEntry);
-
-            responseControls.add(responseControl);
-          }
-
-          setResultCode(ResultCode.SUCCESS);
-        }
-        catch (DirectoryException de)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
-          }
-
-          setResultCode(de.getResultCode());
-          appendErrorMessage(de.getErrorMessage());
-          setMatchedDN(de.getMatchedDN());
-          setReferralURLs(de.getReferralURLs());
-
-          break deleteProcessing;
-        }
-        catch (CancelledOperationException coe)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
-          }
-
-          CancelResult cancelResult = coe.getCancelResult();
-
-          setCancelResult(cancelResult);
-          setResultCode(cancelResult.getResultCode());
-
-          String message = coe.getMessage();
-          if ((message != null) && (message.length() > 0))
-          {
-            appendErrorMessage(message);
-          }
-
-          break deleteProcessing;
-        }
-      }
-      finally
-      {
-        LockManager.unlock(entryDN, entryLock);
-
-        for (SynchronizationProvider provider :
-             DirectoryServer.getSynchronizationProviders())
-        {
-          try
-          {
-            provider.doPostOperation(this);
-          }
-          catch (DirectoryException de)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, de);
-            }
-
-            logError(ErrorLogCategory.SYNCHRONIZATION,
-                     ErrorLogSeverity.SEVERE_ERROR,
-                     MSGID_DELETE_SYNCH_POSTOP_FAILED, 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 delete plugins.
-    if (! skipPostOperation)
-    {
-      PostOperationPluginResult postOperationResult =
-           pluginConfigManager.invokePostOperationDeletePlugins(this);
-      if (postOperationResult.connectionTerminated())
-      {
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-        logDeleteResponse(this);
-        pluginConfigManager.invokePostResponseDeletePlugins(this);
-        return;
-      }
-    }
-
-
-    // Notify any change notification listeners that might be registered with
-    // the server.
-    if (getResultCode() == ResultCode.SUCCESS)
-    {
-      for (ChangeNotificationListener changeListener :
-           DirectoryServer.getChangeNotificationListeners())
-      {
-        try
-        {
-          changeListener.handleDeleteOperation(this, entry);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER;
-          String message = getMessage(msgID, getExceptionMessage(e));
-          logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
-                   message, msgID);
-        }
-      }
-    }
-
-
-    // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
-
-
-    // Send the delete response to the client.
-    getClientConnection().sendResponse(this);
-
-
-    // Log the delete response.
-    logDeleteResponse(this);
-
-
-    // Notify any persistent searches that might be registered with the server.
-    if (getResultCode() == ResultCode.SUCCESS)
-    {
-      for (PersistentSearch persistentSearch :
-           DirectoryServer.getPersistentSearches())
-      {
-        try
-        {
-          persistentSearch.processDelete(this, entry);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_DELETE_ERROR_NOTIFYING_PERSISTENT_SEARCH;
-          String message = getMessage(msgID, String.valueOf(persistentSearch),
-                                      getExceptionMessage(e));
-          logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
-                   message, msgID);
-
-          DirectoryServer.deregisterPersistentSearch(persistentSearch);
-        }
-      }
-    }
-
-
-    // Invoke the post-response delete plugins.
-    pluginConfigManager.invokePostResponseDeletePlugins(this);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelResult cancel(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-
-    CancelResult cancelResult = getCancelResult();
-    long stopWaitingTime = System.currentTimeMillis() + 5000;
-    while ((cancelResult == null) &&
-           (System.currentTimeMillis() < stopWaitingTime))
-    {
-      try
-      {
-        Thread.sleep(50);
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-      }
-
-      cancelResult = getCancelResult();
-    }
-
-    if (cancelResult == null)
-    {
-      // This can happen in some rare cases (e.g., if a client disconnects and
-      // there is still a lot of data to send to that client), and in this case
-      // we'll prevent the cancel thread from blocking for a long period of
-      // time.
-      cancelResult = CancelResult.CANNOT_CANCEL;
-    }
-
-    return cancelResult;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelRequest getCancelRequest()
-  {
-    return cancelRequest;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-    return true;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void toString(StringBuilder buffer)
-  {
-    buffer.append("DeleteOperation(connID=");
-    buffer.append(clientConnection.getConnectionID());
-    buffer.append(", opID=");
-    buffer.append(operationID);
-    buffer.append(", dn=");
-    buffer.append(rawEntryDN);
-    buffer.append(")");
-  }
-}
-
+}
\ No newline at end of file
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperationBasis.java b/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperationBasis.java
new file mode 100644
index 0000000..a930178
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperationBasis.java
@@ -0,0 +1,671 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ENTRY_DN;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ERROR_MESSAGE;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_MATCHED_DN;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_PROCESSING_TIME;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_REFERRAL_URLS;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
+import static org.opends.server.loggers.AccessLogger.logDeleteRequest;
+import static org.opends.server.loggers.AccessLogger.logDeleteResponse;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.messages.CoreMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.plugin.PreParsePluginResult;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.AbstractOperation;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.Operation;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.operation.PostResponseDeleteOperation;
+import org.opends.server.types.operation.PreParseDeleteOperation;
+import org.opends.server.workflowelement.localbackend.*;
+
+
+
+/**
+ * This class defines an operation that may be used to remove an entry from the
+ * Directory Server.
+ */
+public class DeleteOperationBasis
+       extends AbstractOperation
+       implements PreParseDeleteOperation,
+                  DeleteOperation,
+                  PostResponseDeleteOperation
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
+
+  // The raw, unprocessed entry DN as included in the client request.
+  private ByteString rawEntryDN;
+
+  // The cancel request that has been issued for this delete operation.
+  private CancelRequest cancelRequest;
+
+  // The DN of the entry for the delete operation.
+  private DN entryDN;
+
+  // The proxied authorization target DN for this operation.
+  private DN proxiedAuthorizationDN;
+
+  // The set of response controls for this delete operation.
+  private List<Control> responseControls;
+
+  // The change number that has been assigned to this operation.
+  private long changeNumber;
+
+
+  /**
+   * Creates a new delete operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  rawEntryDN        The raw, unprocessed DN of the entry to delete,
+   *                           as included in the client request.
+   */
+  public DeleteOperationBasis(ClientConnection clientConnection,
+                         long operationID,
+                         int messageID, List<Control> requestControls,
+                         ByteString rawEntryDN)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.rawEntryDN = rawEntryDN;
+
+    entryDN          = null;
+    responseControls = new ArrayList<Control>();
+    cancelRequest    = null;
+    changeNumber     = -1;
+  }
+
+
+
+  /**
+   * Creates a new delete operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  entryDN           The entry DN for this delete operation.
+   */
+  public DeleteOperationBasis(ClientConnection clientConnection,
+                         long operationID,
+                         int messageID, List<Control> requestControls,
+                         DN entryDN)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.entryDN = entryDN;
+
+    rawEntryDN       = new ASN1OctetString(entryDN.toString());
+    responseControls = new ArrayList<Control>();
+    cancelRequest    = null;
+    changeNumber     = -1;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ByteString getRawEntryDN()
+  {
+    return rawEntryDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawEntryDN(ByteString rawEntryDN)
+  {
+    this.rawEntryDN = rawEntryDN;
+
+    entryDN = null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DN getEntryDN()
+  {
+    try
+    {
+      if (entryDN == null)
+      {
+        entryDN = DN.decode(rawEntryDN);
+      }
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      setResultCode(de.getResultCode());
+      appendErrorMessage(de.getErrorMessage());
+      setMatchedDN(de.getMatchedDN());
+      setReferralURLs(de.getReferralURLs());
+    }
+
+    return entryDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getChangeNumber()
+  {
+    return changeNumber;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setChangeNumber(long changeNumber)
+  {
+    this.changeNumber = changeNumber;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final OperationType getOperationType()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    return OperationType.DELETE;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void disconnectClient(DisconnectReason disconnectReason,
+                                     boolean sendNotification, String message,
+                                     int messageID)
+  {
+    // Before calling clientConnection.disconnect, we need to mark this
+    // operation as cancelled so that the attempt to cancel it later won't cause
+    // an unnecessary delay.
+    setCancelResult(CancelResult.CANCELED);
+
+    clientConnection.disconnect(disconnectReason, sendNotification, message,
+                                messageID);
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final String[][] getRequestLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    return new String[][]
+    {
+      new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) }
+    };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final String[][] getResponseLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    String resultCode = String.valueOf(getResultCode().getIntValue());
+
+    String errorMessage;
+    StringBuilder errorMessageBuffer = getErrorMessage();
+    if (errorMessageBuffer == null)
+    {
+      errorMessage = null;
+    }
+    else
+    {
+      errorMessage = errorMessageBuffer.toString();
+    }
+
+    String matchedDNStr;
+    DN matchedDN = getMatchedDN();
+    if (matchedDN == null)
+    {
+      matchedDNStr = null;
+    }
+    else
+    {
+      matchedDNStr = matchedDN.toString();
+    }
+
+    String referrals;
+    List<String> referralURLs = getReferralURLs();
+    if ((referralURLs == null) || referralURLs.isEmpty())
+    {
+      referrals = null;
+    }
+    else
+    {
+      StringBuilder buffer = new StringBuilder();
+      Iterator<String> iterator = referralURLs.iterator();
+      buffer.append(iterator.next());
+
+      while (iterator.hasNext())
+      {
+        buffer.append(", ");
+        buffer.append(iterator.next());
+      }
+
+      referrals = buffer.toString();
+    }
+
+    String processingTime =
+         String.valueOf(getProcessingTime());
+
+    return new String[][]
+    {
+      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
+      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
+      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
+      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
+      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
+    };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return proxiedAuthorizationDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final List<Control> getResponseControls()
+  {
+    return responseControls;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void addResponseControl(Control control)
+  {
+    responseControls.add(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void removeResponseControl(Control control)
+  {
+    responseControls.remove(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final CancelResult cancel(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+
+    CancelResult cancelResult = getCancelResult();
+    long stopWaitingTime = System.currentTimeMillis() + 5000;
+    while ((cancelResult == null) &&
+           (System.currentTimeMillis() < stopWaitingTime))
+    {
+      try
+      {
+        Thread.sleep(50);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+      }
+
+      cancelResult = getCancelResult();
+    }
+
+    if (cancelResult == null)
+    {
+      // This can happen in some rare cases (e.g., if a client disconnects and
+      // there is still a lot of data to send to that client), and in this case
+      // we'll prevent the cancel thread from blocking for a long period of
+      // time.
+      cancelResult = CancelResult.CANNOT_CANCEL;
+    }
+
+    return cancelResult;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final CancelRequest getCancelRequest()
+  {
+    return cancelRequest;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public
+  boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+    return true;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void toString(StringBuilder buffer)
+  {
+    buffer.append("DeleteOperation(connID=");
+    buffer.append(clientConnection.getConnectionID());
+    buffer.append(", opID=");
+    buffer.append(operationID);
+    buffer.append(", dn=");
+    buffer.append(rawEntryDN);
+    buffer.append(")");
+  }
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
+  {
+    this.proxiedAuthorizationDN = proxiedAuthorizationDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void run()
+  {
+    setResultCode(ResultCode.UNDEFINED);
+
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+         DirectoryServer.getPluginConfigManager();
+
+    // Start the processing timer.
+    setProcessingStartTime();
+
+    // Check for and handle a request to cancel this operation.
+    if (getCancelRequest() != null)
+    {
+      indicateCancelled(getCancelRequest());
+      setProcessingStopTime();
+      return;
+    }
+
+    // Create a labeled block of code that we can break out of if a problem is
+    // detected.
+deleteProcessing:
+    {
+      // Invoke the pre-parse delete plugins.
+      PreParsePluginResult preParseResult =
+           pluginConfigManager.invokePreParseDeletePlugins(this);
+      if (preParseResult.connectionTerminated())
+      {
+        // There's no point in continuing with anything.  Log the request and
+        // result and return.
+        setResultCode(ResultCode.CANCELED);
+
+        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
+        appendErrorMessage(getMessage(msgID));
+
+        setProcessingStopTime();
+
+        logDeleteRequest(this);
+        logDeleteResponse(this);
+        pluginConfigManager.invokePostResponseDeletePlugins(this);
+        return;
+      }
+      else if (preParseResult.sendResponseImmediately())
+      {
+        logDeleteRequest(this);
+        break deleteProcessing;
+      }
+      else if (preParseResult.skipCoreProcessing())
+      {
+        break deleteProcessing;
+      }
+
+
+      // Log the delete request message.
+      logDeleteRequest(this);
+
+
+      // Check for and handle a request to cancel this operation.
+      if (getCancelRequest() != null)
+      {
+        indicateCancelled(getCancelRequest());
+        setProcessingStopTime();
+        logDeleteResponse(this);
+        pluginConfigManager.invokePostResponseDeletePlugins(this);
+        return;
+      }
+
+
+      // Process the entry DN to convert it from its raw form as provided by the
+      // client to the form required for the rest of the delete processing.
+      DN entryDN = getEntryDN();
+      if (entryDN == null){
+        break deleteProcessing;
+      }
+
+
+      // Retrieve the network group attached to the client connection
+      // and get a workflow to process the operation.
+      NetworkGroup ng = getClientConnection().getNetworkGroup();
+      Workflow workflow = ng.getWorkflowCandidate(entryDN);
+      if (workflow == null)
+      {
+        // We have found no workflow for the requested base DN, just return
+        // a no such entry result code and stop the processing.
+        updateOperationErrMsgAndResCode();
+        break deleteProcessing;
+      }
+      workflow.execute(this);
+    }
+
+    // Check for and handle a request to cancel this operation.
+    if ((getCancelRequest() != null) ||
+        (getCancelResult() == CancelResult.CANCELED))
+    {
+      if (getCancelRequest() != null){
+        indicateCancelled(getCancelRequest());
+      }
+      setProcessingStopTime();
+      logDeleteResponse(this);
+      invokePostResponsePlugins();
+      return;
+    }
+
+    // Indicate that it is now too late to attempt to cancel the operation.
+    setCancelResult(CancelResult.TOO_LATE);
+
+    // Stop the processing timer.
+    setProcessingStopTime();
+
+
+    // Send the delete response to the client.
+    getClientConnection().sendResponse(this);
+
+
+    // Log the delete response.
+    logDeleteResponse(this);
+
+    // Check wether there are local operations in attachments
+    List localOperations =
+      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
+    if (localOperations != null && (! localOperations.isEmpty())){
+      for (Object localOp : localOperations)
+      {
+        LocalBackendDeleteOperation localOperation =
+          (LocalBackendDeleteOperation)localOp;
+        // Notify any persistent searches that might be registered with the
+        // server.
+        if (getResultCode() == ResultCode.SUCCESS)
+        {
+          for (PersistentSearch persistentSearch :
+            DirectoryServer.getPersistentSearches())
+          {
+            try
+            {
+              persistentSearch.processDelete(localOperation,
+                  localOperation.getEntryToDelete());
+            }
+            catch (Exception e)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, e);
+              }
+
+              int    msgID   = MSGID_DELETE_ERROR_NOTIFYING_PERSISTENT_SEARCH;
+              String message = getMessage(msgID,
+                  String.valueOf(persistentSearch),
+                  getExceptionMessage(e));
+              logError(ErrorLogCategory.CORE_SERVER,
+                  ErrorLogSeverity.SEVERE_ERROR,
+                  message, msgID);
+
+              DirectoryServer.deregisterPersistentSearch(persistentSearch);
+            }
+          }
+        }
+
+        // Invoke the post-response delete plugins.
+        pluginConfigManager.invokePostResponseDeletePlugins(localOperation);
+      }
+    }
+  }
+
+  /**
+   * Updates the error message and the result code of the operation.
+   *
+   * This method is called because no workflows were found to process
+   * the operation.
+   */
+  private void updateOperationErrMsgAndResCode()
+  {
+    setResultCode(ResultCode.NO_SUCH_OBJECT);
+    appendErrorMessage(getMessage(MSGID_DELETE_NO_SUCH_ENTRY,
+                                  String.valueOf(getEntryDN())));
+  }
+
+  /**
+   * Execute the postResponseDeletePlugins.
+   */
+  private void invokePostResponsePlugins()
+  {
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+      DirectoryServer.getPluginConfigManager();
+
+    // Check wether there are local operations in attachments
+    List localOperations =
+      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
+
+    if (localOperations != null && (! localOperations.isEmpty())){
+      for (Object localOp : localOperations)
+      {
+        LocalBackendDeleteOperation localOperation =
+          (LocalBackendDeleteOperation)localOp;
+        // Invoke the post-response delete plugins.
+        pluginConfigManager.invokePostResponseDeletePlugins(localOperation);
+      }
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * This method always returns null.
+   */
+  public Entry getEntryToDelete() {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperationWrapper.java b/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperationWrapper.java
new file mode 100644
index 0000000..4858485
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperationWrapper.java
@@ -0,0 +1,569 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.ResultCode;
+
+/**
+ * This abstract class wraps/decorates a given delete operation.
+ * This class will be extended by sub-classes to enhance the
+ * functionnality of the DeleteOperationBasis.
+ */
+public abstract class DeleteOperationWrapper implements DeleteOperation
+{
+  DeleteOperation delete;
+
+  /**
+   * Creates a new delete operation based on the provided delete operation.
+   *
+   * @param delete The delete operation to wrap
+   */
+  public DeleteOperationWrapper(DeleteOperation delete){
+    this.delete = delete;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addRequestControl(Control control)
+  {
+    delete.addRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addResponseControl(Control control)
+  {
+    delete.addResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendAdditionalLogMessage(String message)
+  {
+    delete.appendAdditionalLogMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendErrorMessage(String message)
+  {
+    delete.appendErrorMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult cancel(CancelRequest cancelRequest)
+  {
+    return delete.cancel(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void disconnectClient(DisconnectReason disconnectReason,
+      boolean sendNotification, String message, int messageID)
+  {
+    delete.disconnectClient(disconnectReason, sendNotification,
+        message, messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean dontSynchronize()
+  {
+    return delete.dontSynchronize();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getAdditionalLogMessage()
+  {
+    return delete.getAdditionalLogMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object getAttachment(String name)
+  {
+    return delete.getAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<String, Object> getAttachments()
+  {
+    return delete.getAttachments();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getAuthorizationDN()
+  {
+    return delete.getAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry getAuthorizationEntry()
+  {
+    return delete.getAuthorizationEntry();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelRequest getCancelRequest()
+  {
+    return delete.getCancelRequest();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult getCancelResult()
+  {
+    return delete.getCancelResult();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ClientConnection getClientConnection()
+  {
+    return delete.getClientConnection();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getCommonLogElements()
+  {
+    return delete.getCommonLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getConnectionID()
+  {
+    return delete.getConnectionID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getEntryDN()
+  {
+    return delete.getEntryDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getErrorMessage()
+  {
+    return delete.getErrorMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getMatchedDN()
+  {
+    return delete.getMatchedDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getMessageID()
+  {
+    return delete.getMessageID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getOperationID()
+  {
+    return delete.getOperationID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public OperationType getOperationType()
+  {
+    return delete.getOperationType();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStartTime()
+  {
+    return delete.getProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStopTime()
+  {
+    return delete.getProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingTime()
+  {
+    return delete.getProcessingTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getRawEntryDN()
+  {
+    return delete.getRawEntryDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<String> getReferralURLs()
+  {
+    return delete.getReferralURLs();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getRequestControls()
+  {
+    return delete.getRequestControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getRequestLogElements()
+  {
+    return delete.getRequestLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getResponseControls()
+  {
+    return delete.getResponseControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getResponseLogElements()
+  {
+    return delete.getResponseLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ResultCode getResultCode()
+  {
+    return delete.getResultCode();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void indicateCancelled(CancelRequest cancelRequest)
+  {
+    delete.indicateCancelled(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isInternalOperation()
+  {
+    return delete.isInternalOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isSynchronizationOperation()
+  {
+    return delete.isSynchronizationOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void operationCompleted()
+  {
+    delete.operationCompleted();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object removeAttachment(String name)
+  {
+    return delete.removeAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeRequestControl(Control control)
+  {
+    delete.removeRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeResponseControl(Control control)
+  {
+    delete.removeResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAdditionalLogMessage(StringBuilder additionalLogMessage)
+  {
+    delete.setAdditionalLogMessage(additionalLogMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object setAttachment(String name, Object value)
+  {
+    return delete.setAttachment(name, value);
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttachments(Map<String, Object> attachments)
+  {
+    delete.setAttachments(attachments);
+  }
+
+   /**
+    * {@inheritDoc}
+    */
+  public void setAuthorizationEntry(Entry authorizationEntry)
+  {
+    delete.setAuthorizationEntry(authorizationEntry);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    return delete.setCancelRequest(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setCancelResult(CancelResult cancelResult)
+  {
+    delete.setCancelResult(cancelResult);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setDontSynchronize(boolean dontSynchronize)
+  {
+    delete.setDontSynchronize(dontSynchronize);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setErrorMessage(StringBuilder errorMessage)
+  {
+    delete.setErrorMessage(errorMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setInternalOperation(boolean isInternalOperation)
+  {
+    delete.setInternalOperation(isInternalOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setMatchedDN(DN matchedDN)
+  {
+    delete.setMatchedDN(matchedDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStartTime()
+  {
+    delete.setProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStopTime()
+  {
+    delete.setProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawEntryDN(ByteString rawEntryDN)
+  {
+    delete.setRawEntryDN(rawEntryDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setReferralURLs(List<String> referralURLs)
+  {
+    delete.setReferralURLs(referralURLs);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResponseData(DirectoryException directoryException)
+  {
+    delete.setResponseData(directoryException);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResultCode(ResultCode resultCode)
+  {
+    delete.setResultCode(resultCode);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSynchronizationOperation(boolean isSynchronizationOperation)
+  {
+    delete.setSynchronizationOperation(isSynchronizationOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getChangeNumber()
+  {
+    return delete.getChangeNumber();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setChangeNumber(long changeNumber)
+  {
+    delete.setChangeNumber(changeNumber);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return delete.toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void toString(StringBuilder buffer)
+  {
+    delete.toString(buffer);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return delete.getProxiedAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
+  {
+    delete.setProxiedAuthorizationDN(proxiedAuthorizationDN);
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java b/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
index 40a99ee..3b741dd 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -101,6 +101,8 @@
 import static org.opends.server.util.StaticUtils.*;
 import static org.opends.server.util.Validator.*;
 import com.sleepycat.je.JEVersion;
+import org.opends.server.workflowelement.WorkflowElement;
+import org.opends.server.workflowelement.localbackend.*;
 
 
 
@@ -1114,6 +1116,12 @@
       // Initialize all the backends and their associated suffixes.
       initializeBackends();
 
+      // A first set of workflows had been created in the registerBackend
+      // method. We now need to complete the workflow creation for the
+      // backends that were not registered through the registerBackend
+      // method (ie. cn=config and RootDSE).
+      createAndRegisterRemainingWorkflows();
+
       // Check for and initialize user configured entry cache if any,
       // if not stick with default entry cache initialized earlier.
       entryCacheConfigManager.initializeEntryCache();
@@ -2251,6 +2259,102 @@
   }
 
 
+  /**
+   * Deregister a workflow from a network group.
+   *
+   * In the first implementation, workflows are stored in the default network
+   * group.
+   *
+   * @param backend  the backend which is handled by the workflows to
+   *                 deregister
+   */
+  private static void deregisterWorkflows(
+      Backend backend
+      )
+  {
+    // Get the default network group and deregister all the workflows
+    // being configured for the backend (reminder: there is one worklfow
+    // per base DN configured in the backend).
+    NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
+    for (DN baseDN: backend.getBaseDNs())
+    {
+      defaultNetworkGroup.deregisterWorkflow (baseDN);
+    }
+  }
+
+
+  /**
+   * Create a workflow for the a backend then register the workflow
+   * with the appropriate network group.
+   *
+   * TODO implement the registration with the appropriate network group.
+   *
+   * @param backend  the backend handled by the workflow
+   */
+  private static void createAndRegisterWorkflows(
+      Backend backend
+      )
+  {
+    // Create a root workflow element to encapsulate the backend
+    LocalBackendWorkflowElement rootWE =
+        new LocalBackendWorkflowElement(backend);
+
+    // Create a worklfow for each baseDN being configured
+    // in the backend and register the workflow with the network groups
+    for (DN curBaseDN: backend.getBaseDNs())
+    {
+      WorkflowImpl workflowImpl = new WorkflowImpl(
+          curBaseDN, (WorkflowElement) rootWE);
+      registerWorkflowInNetworkGroups(workflowImpl);
+    }
+  }
+
+
+  /**
+   * Register a workflow with the appropriate network groups.
+   *
+   * In the first implementation, the workflow is registered with the
+   * default network group only.
+   *
+   * TODO implement the registration with the appropriate network group.
+   *
+   * @param workflowImpl  the workflow to register
+   */
+  private static void registerWorkflowInNetworkGroups(
+      WorkflowImpl workflowImpl
+      )
+  {
+    // Register first the workflow with the default network group
+    NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
+    defaultNetworkGroup.registerWorkflow(workflowImpl);
+
+    // Now for each network group that exposes the baseDN of the workflow
+    // create an instance of the workflow and register it with the network
+    // group.
+    // TODO jdemendi - we need the network group configuration to configure
+    // the workflows per network group.
+  }
+
+
+  /**
+   * Create the workflows for the backends that were not registered through
+   * registerBackend method, nemely cn=config and RootDSE.
+   *
+   * TODO jdemendi - read the Workflow config and create them accordingly
+   *
+   * For the prototype: there is no configuration for the workflows.
+   * So we create one workflow per local backend, and we register it
+   * to the pool.
+   */
+  private void createAndRegisterRemainingWorkflows()
+  {
+    // Create a workflow for the cn=config backend
+    createAndRegisterWorkflows (configHandler);
+
+    // Create a workflows for the rootDSE backend
+    createAndRegisterWorkflows (rootDSEBackend);
+  }
+
 
   /**
    * Initializes the Directory Server group manager.
@@ -6006,6 +6110,15 @@
         monitor.initializeMonitorProvider(null);
         backend.setBackendMonitor(monitor);
         registerMonitorProvider(monitor);
+
+        // FIXME jdemendi - temporary code: create one workflow for each
+        // base DN being configured in the backend. We should not rely on the
+        // backend registration to create and register workflows because
+        // a workflow can be created for different type of "backend", including
+        // remote lDAP server. Instead, we should create the workflows using
+        // the administration framework. We will be doing so as soon as we
+        // have a configuration section for the workflows.
+        createAndRegisterWorkflows (backend);
       }
     }
   }
@@ -6032,6 +6145,13 @@
 
       directoryServer.backends = newBackends;
 
+      // Delete all the workflows registered for the backend.
+      // FIXME jdemendi - This task should be performed in the scope of the
+      // administration framework. However the administration framework
+      // requires a configuration section for the workflows which we don't have
+      // as of today.
+      deregisterWorkflows (backend);
+
       BackendMonitor monitor = backend.getBackendMonitor();
       if (monitor != null)
       {
@@ -7190,7 +7310,7 @@
    * @throws  DirectoryException  If a problem prevents the operation from being
    *                              added to the queue (e.g., the queue is full).
    */
-  public static void enqueueRequest(Operation operation)
+  public static void enqueueRequest(AbstractOperation operation)
          throws DirectoryException
   {
     // See if a bind is already in progress on the associated connection.  If so
@@ -7236,7 +7356,7 @@
           }
 
         case EXTENDED:
-         ExtendedOperation extOp      = (ExtendedOperation) operation;
+         ExtendedOperation extOp = (ExtendedOperation) operation;
          String   requestOID = extOp.getRequestOID();
          if (!((requestOID != null) &&
                  requestOID.equals(OID_START_TLS_REQUEST)))
@@ -7283,7 +7403,7 @@
         case EXTENDED:
           // We will only allow the password modify and StartTLS extended
           // operations.
-          ExtendedOperation extOp      = (ExtendedOperation) operation;
+          ExtendedOperation extOp = (ExtendedOperation) operation;
           String            requestOID = extOp.getRequestOID();
           if ((requestOID == null) ||
               ((! requestOID.equals(OID_PASSWORD_MODIFY_REQUEST)) &&
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ExtendedOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/ExtendedOperation.java
index 8268157..a683fc1 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/ExtendedOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/ExtendedOperation.java
@@ -38,12 +38,12 @@
 import org.opends.server.api.plugin.PreOperationPluginResult;
 import org.opends.server.api.plugin.PreParsePluginResult;
 import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.CancelRequest;
 import org.opends.server.types.CancelResult;
 import org.opends.server.types.Control;
 import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.DN;
-import org.opends.server.types.Operation;
 import org.opends.server.types.OperationType;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.operation.PostOperationExtendedOperation;
@@ -54,6 +54,8 @@
 import static org.opends.server.core.CoreConstants.*;
 import static org.opends.server.loggers.AccessLogger.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
+
+import org.opends.server.loggers.debug.DebugLogger;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.types.DebugLogLevel;
 import static org.opends.server.messages.CoreMessages.*;
@@ -67,17 +69,14 @@
  * kind of task.
  */
 public class ExtendedOperation
-       extends Operation
+       extends AbstractOperation
        implements PreParseExtendedOperation, PreOperationExtendedOperation,
                   PostOperationExtendedOperation, PostResponseExtendedOperation
 {
   /**
    * The tracer object for the debug logger.
    */
-  private static final DebugTracer TRACER = getTracer();
-
-
-
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
 
   // The value for the request associated with this extended operation.
   private ASN1OctetString requestValue;
@@ -94,12 +93,6 @@
   // The set of response controls for this extended operation.
   private List<Control> responseControls;
 
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
   // The OID for the request associated with this extended operation.
   private String requestOID;
 
@@ -245,40 +238,6 @@
   }
 
 
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
-
   /**
    * {@inheritDoc}
    */
@@ -384,7 +343,7 @@
     }
 
     String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
+         String.valueOf(getProcessingTime());
 
     return new String[][]
     {
@@ -433,9 +392,12 @@
 
 
   /**
-   * {@inheritDoc}
+   * Performs the work of actually processing this operation.  This
+   * should include all processing for the operation, including
+   * invoking plugins, logging messages, performing access control,
+   * managing synchronization, and any other work that might need to
+   * be done in the course of processing.
    */
-  @Override()
   public final void run()
   {
     setResultCode(ResultCode.UNDEFINED);
@@ -448,7 +410,7 @@
 
 
     // Start the processing timer.
-    processingStartTime = System.currentTimeMillis();
+    setProcessingStartTime();
 
 
     // Check for and handle a request to cancel this operation.
@@ -458,7 +420,7 @@
              requestOID.equals(OID_START_TLS_REQUEST)))
       {
         indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
         return;
       }
     }
@@ -480,7 +442,7 @@
         int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
         appendErrorMessage(getMessage(msgID));
 
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
 
         logExtendedRequest(this);
         logExtendedResponse(this);
@@ -511,7 +473,7 @@
                requestOID.equals(OID_START_TLS_REQUEST)))
         {
           indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
+          setProcessingStopTime();
           pluginConfigManager.invokePostResponseExtendedPlugins(this);
           return;
         }
@@ -586,7 +548,7 @@
         int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
         appendErrorMessage(getMessage(msgID));
 
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
 
         logExtendedResponse(this);
         pluginConfigManager.invokePostResponseExtendedPlugins(this);
@@ -611,7 +573,7 @@
                requestOID.equals(OID_START_TLS_REQUEST)))
         {
           indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
+          setProcessingStopTime();
           pluginConfigManager.invokePostResponseExtendedPlugins(this);
           return;
         }
@@ -641,7 +603,7 @@
         int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
         appendErrorMessage(getMessage(msgID));
 
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
 
         logExtendedResponse(this);
         pluginConfigManager.invokePostResponseExtendedPlugins(this);
@@ -651,7 +613,7 @@
 
 
     // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
+    setProcessingStopTime();
 
 
     // Send the response to the client, if it has not already been sent.
@@ -765,7 +727,7 @@
    * {@inheritDoc}
    */
   @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
+  public boolean setCancelRequest(CancelRequest cancelRequest)
   {
     this.cancelRequest = cancelRequest;
     return true;
@@ -787,5 +749,6 @@
     buffer.append(requestOID);
     buffer.append(")");
   }
+
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/GroupManager.java b/opendj-sdk/opends/src/server/org/opends/server/core/GroupManager.java
index fc9756a..c7a0416 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/GroupManager.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/GroupManager.java
@@ -35,6 +35,8 @@
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+
+import org.opends.server.workflowelement.localbackend.*;
 import org.opends.server.admin.ClassPropertyDefinition;
 import org.opends.server.admin.server.ConfigurationAddListener;
 import org.opends.server.admin.server.ConfigurationChangeListener;
@@ -598,9 +600,11 @@
                                          SearchScope.WHOLE_SUBTREE,
                                          DereferencePolicy.NEVER_DEREF_ALIASES,
                                          0, 0, false, filter, null, null);
+        LocalBackendSearchOperation localSearch =
+          new LocalBackendSearchOperation(internalSearch);
         try
         {
-          backend.search(internalSearch);
+          backend.search(localSearch);
         }
         catch (Exception e)
         {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyDNOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyDNOperation.java
index 22f41ef..9c01614 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyDNOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyDNOperation.java
@@ -50,6 +50,7 @@
 import org.opends.server.controls.ProxiedAuthV1Control;
 import org.opends.server.controls.ProxiedAuthV2Control;
 import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValue;
@@ -68,7 +69,6 @@
 import org.opends.server.types.LockManager;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
-import org.opends.server.types.Operation;
 import org.opends.server.types.OperationType;
 import org.opends.server.types.Privilege;
 import org.opends.server.types.RDN;
@@ -86,6 +86,8 @@
 import org.opends.server.types.DebugLogLevel;
 import static org.opends.server.loggers.ErrorLogger.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
+
+import org.opends.server.loggers.debug.DebugLogger;
 import org.opends.server.loggers.debug.DebugTracer;
 import static org.opends.server.messages.CoreMessages.*;
 import static org.opends.server.messages.MessageHandler.*;
@@ -99,14 +101,14 @@
  * in the Directory Server.
  */
 public class ModifyDNOperation
-       extends Operation
+       extends AbstractOperation
        implements PreParseModifyDNOperation, PreOperationModifyDNOperation,
                   PostOperationModifyDNOperation, PostResponseModifyDNOperation
 {
   /**
    * The tracer object for the debug logger.
    */
-  private static final DebugTracer TRACER = getTracer();
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
 
   // Indicates whether to delete the old RDN value from the entry.
   private boolean deleteOldRDN;
@@ -150,12 +152,6 @@
   // The change number that has been assigned to this operation.
   private long changeNumber;
 
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
   // The new RDN for the entry.
   private RDN newRDN;
 
@@ -490,41 +486,6 @@
     return newEntry;
   }
 
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
-
   /**
    * Retrieves the change number that has been assigned to this operation.
    *
@@ -671,7 +632,7 @@
     }
 
     String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
+         String.valueOf(getProcessingTime());
 
     return new String[][]
     {
@@ -734,9 +695,12 @@
 
 
   /**
-   * {@inheritDoc}
+   * Performs the work of actually processing this operation.  This
+   * should include all processing for the operation, including
+   * invoking plugins, logging messages, performing access control,
+   * managing synchronization, and any other work that might need to
+   * be done in the course of processing.
    */
-  @Override()
   public final void run()
   {
     setResultCode(ResultCode.UNDEFINED);
@@ -749,14 +713,14 @@
 
 
     // Start the processing timer.
-    processingStartTime = System.currentTimeMillis();
+    setProcessingStartTime();
 
 
     // Check for and handle a request to cancel this operation.
     if (cancelRequest != null)
     {
       indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
+      setProcessingStopTime();
       return;
     }
 
@@ -777,7 +741,7 @@
         int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
         appendErrorMessage(getMessage(msgID));
 
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
 
         logModifyDNRequest(this);
         logModifyDNResponse(this);
@@ -805,7 +769,7 @@
       if (cancelRequest != null)
       {
         indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
         logModifyDNResponse(this);
         pluginConfigManager.invokePostResponseModifyDNPlugins(this);
         return;
@@ -941,7 +905,7 @@
       if (cancelRequest != null)
       {
         indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
         logModifyDNResponse(this);
         pluginConfigManager.invokePostResponseModifyDNPlugins(this);
         return;
@@ -1025,7 +989,7 @@
         if (cancelRequest != null)
         {
           indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
+          setProcessingStopTime();
           logModifyDNResponse(this);
           pluginConfigManager.invokePostResponseModifyDNPlugins(this);
           return;
@@ -1571,7 +1535,7 @@
         if (cancelRequest != null)
         {
           indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
+          setProcessingStopTime();
           logModifyDNResponse(this);
           pluginConfigManager.invokePostResponseModifyDNPlugins(this);
           return;
@@ -1599,7 +1563,7 @@
             int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
             appendErrorMessage(getMessage(msgID));
 
-            processingStopTime = System.currentTimeMillis();
+            setProcessingStopTime();
             logModifyDNResponse(this);
             pluginConfigManager.invokePostResponseModifyDNPlugins(this);
             return;
@@ -1798,7 +1762,7 @@
         if (cancelRequest != null)
         {
           indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
+          setProcessingStopTime();
           logModifyDNResponse(this);
           pluginConfigManager.invokePostResponseModifyDNPlugins(this);
           return;
@@ -2078,7 +2042,7 @@
         int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
         appendErrorMessage(getMessage(msgID));
 
-        processingStopTime = System.currentTimeMillis();
+        setProcessingStopTime();
         logModifyDNResponse(this);
         pluginConfigManager.invokePostResponseModifyDNPlugins(this);
         return;
@@ -2114,7 +2078,7 @@
 
 
     // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
+    setProcessingStopTime();
 
 
     // Send the modify DN response to the client.
@@ -2217,7 +2181,7 @@
    * {@inheritDoc}
    */
   @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
+  public boolean setCancelRequest(CancelRequest cancelRequest)
   {
     this.cancelRequest = cancelRequest;
     return true;
@@ -2250,5 +2214,6 @@
 
     buffer.append(")");
   }
+
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
index e9661d4..ab03679 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -26,244 +26,29 @@
  */
 package org.opends.server.core;
 
-
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.concurrent.locks.Lock;
 
-import org.opends.server.api.AttributeSyntax;
-import org.opends.server.api.Backend;
-import org.opends.server.api.ChangeNotificationListener;
-import org.opends.server.api.ClientConnection;
-import org.opends.server.api.PasswordStorageScheme;
-import org.opends.server.api.SynchronizationProvider;
-import org.opends.server.api.plugin.PostOperationPluginResult;
-import org.opends.server.api.plugin.PreOperationPluginResult;
-import org.opends.server.api.plugin.PreParsePluginResult;
-import org.opends.server.controls.LDAPAssertionRequestControl;
-import org.opends.server.controls.LDAPPreReadRequestControl;
-import org.opends.server.controls.LDAPPreReadResponseControl;
-import org.opends.server.controls.LDAPPostReadRequestControl;
-import org.opends.server.controls.LDAPPostReadResponseControl;
-import org.opends.server.controls.ProxiedAuthV1Control;
-import org.opends.server.controls.ProxiedAuthV2Control;
-import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.protocols.ldap.LDAPAttribute;
-import org.opends.server.protocols.ldap.LDAPModification;
-import org.opends.server.schema.AuthPasswordSyntax;
-import org.opends.server.schema.BooleanSyntax;
-import org.opends.server.schema.UserPasswordSyntax;
-import org.opends.server.types.AcceptRejectWarn;
-import org.opends.server.types.AccountStatusNotificationType;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.AttributeValue;
-import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.ByteString;
-import org.opends.server.types.CancelledOperationException;
-import org.opends.server.types.CancelRequest;
-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.DisconnectReason;
 import org.opends.server.types.DN;
-import org.opends.server.types.Entry;
-import org.opends.server.types.ErrorLogCategory;
-import org.opends.server.types.ErrorLogSeverity;
-import org.opends.server.types.LDAPException;
-import org.opends.server.types.LockManager;
+import org.opends.server.types.DirectoryException;
 import org.opends.server.types.Modification;
-import org.opends.server.types.ModificationType;
 import org.opends.server.types.Operation;
-import org.opends.server.types.OperationType;
-import org.opends.server.types.Privilege;
 import org.opends.server.types.RawModification;
-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.PreParseModifyOperation;
-import org.opends.server.types.operation.PreOperationModifyOperation;
-import org.opends.server.types.operation.PostOperationModifyOperation;
-import org.opends.server.types.operation.PostResponseModifyOperation;
-
-import static org.opends.server.config.ConfigConstants.*;
-import static org.opends.server.core.CoreConstants.*;
-import static org.opends.server.loggers.AccessLogger.*;
-import static org.opends.server.loggers.ErrorLogger.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
-import static org.opends.server.messages.CoreMessages.*;
-import static org.opends.server.messages.MessageHandler.getMessage;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
-
 
 /**
- * This class defines an operation that may be used to modify an entry in the
- * Directory Server.
+ * This interface defines an operation used to modify an entry in
+ * the Directory Server.
  */
-public class ModifyOperation
-       extends Operation
-       implements PreParseModifyOperation, PreOperationModifyOperation,
-                  PostOperationModifyOperation, PostResponseModifyOperation
+public interface ModifyOperation extends Operation
 {
   /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = getTracer();
-
-  // The raw, unprocessed entry DN as included by the client request.
-  private ByteString rawEntryDN;
-
-  // The cancel request that has been issued for this modify operation.
-  private CancelRequest cancelRequest;
-
-  // The DN of the entry for the modify operation.
-  private DN entryDN;
-
-  // The proxied authorization target DN for this operation.
-  private DN proxiedAuthorizationDN;
-
-  // The current entry, before any changes are applied.
-  private Entry currentEntry;
-
-  // The modified entry that will be stored in the backend.
-  private Entry modifiedEntry;
-
-  // The set of clear-text current passwords (if any were provided).
-  private List<AttributeValue> currentPasswords;
-
-  // The set of clear-text new passwords (if any were provided).
-  private List<AttributeValue> newPasswords;
-
-  // The set of response controls for this modify operation.
-  private List<Control> responseControls;
-
-  // The raw, unprocessed set of modifications as included in the client
-  // request.
-  private List<RawModification> rawModifications;
-
-  // The set of modifications for this modify operation.
-  private List<Modification> modifications;
-
-  // The change number that has been assigned to this operation.
-  private long changeNumber;
-
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
-
-
-  /**
-   * Creates a new modify operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  rawEntryDN        The raw, unprocessed DN of the entry to modify,
-   *                           as included in the client request.
-   * @param  rawModifications  The raw, unprocessed set of modifications for
-   *                           this modify operation as included in the client
-   *                           request.
-   */
-  public ModifyOperation(ClientConnection clientConnection, long operationID,
-                         int messageID, List<Control> requestControls,
-                         ByteString rawEntryDN,
-                         List<RawModification> rawModifications)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.rawEntryDN       = rawEntryDN;
-    this.rawModifications = rawModifications;
-
-    entryDN          = null;
-    modifications    = null;
-    currentEntry     = null;
-    modifiedEntry    = null;
-    responseControls = new ArrayList<Control>();
-    cancelRequest    = null;
-    changeNumber     = -1;
-
-    currentPasswords = null;
-    newPasswords     = null;
-  }
-
-
-
-  /**
-   * Creates a new modify operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  entryDN           The entry DN for the modify operation.
-   * @param  modifications     The set of modifications for this modify
-   *                           operation.
-   */
-  public ModifyOperation(ClientConnection clientConnection, long operationID,
-                         int messageID, List<Control> requestControls,
-                         DN entryDN, List<Modification> modifications)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.entryDN       = entryDN;
-    this.modifications = modifications;
-
-    rawEntryDN = new ASN1OctetString(entryDN.toString());
-
-    rawModifications = new ArrayList<RawModification>(modifications.size());
-    for (Modification m : modifications)
-    {
-      rawModifications.add(new LDAPModification(m.getModificationType(),
-                                    new LDAPAttribute(m.getAttribute())));
-    }
-
-    currentEntry     = null;
-    modifiedEntry    = null;
-    responseControls = new ArrayList<Control>();
-    cancelRequest    = null;
-    changeNumber     = -1;
-
-    currentPasswords = null;
-    newPasswords     = null;
-  }
-
-
-
-  /**
    * Retrieves the raw, unprocessed entry DN as included in the client request.
    * The DN that is returned may or may not be a valid DN, since no validation
    * will have been performed upon it.
    *
    * @return  The raw, unprocessed entry DN as included in the client request.
    */
-  public final ByteString getRawEntryDN()
-  {
-    return rawEntryDN;
-  }
-
-
+  public abstract ByteString getRawEntryDN();
 
   /**
    * Specifies the raw, unprocessed entry DN as included in the client request.
@@ -272,14 +57,7 @@
    * @param  rawEntryDN  The raw, unprocessed entry DN as included in the client
    *                     request.
    */
-  public final void setRawEntryDN(ByteString rawEntryDN)
-  {
-    this.rawEntryDN = rawEntryDN;
-
-    entryDN = null;
-  }
-
-
+  public abstract void setRawEntryDN(ByteString rawEntryDN);
 
   /**
    * Retrieves the DN of the entry to modify.  This should not be called by
@@ -289,12 +67,7 @@
    * @return  The DN of the entry to modify, or <CODE>null</CODE> if the raw
    *          entry DN has not yet been processed.
    */
-  public final DN getEntryDN()
-  {
-    return entryDN;
-  }
-
-
+  public abstract DN getEntryDN();
 
   /**
    * Retrieves the set of raw, unprocessed modifications as included in the
@@ -305,12 +78,7 @@
    * @return  The set of raw, unprocessed modifications as included in the
    *          client request.
    */
-  public final List<RawModification> getRawModifications()
-  {
-    return rawModifications;
-  }
-
-
+  public abstract List<RawModification> getRawModifications();
 
   /**
    * Adds the provided modification to the set of raw modifications for this
@@ -319,28 +87,15 @@
    * @param  rawModification  The modification to add to the set of raw
    *                          modifications for this modify operation.
    */
-  public final void addRawModification(RawModification rawModification)
-  {
-    rawModifications.add(rawModification);
-
-    modifications = null;
-  }
-
-
+  public abstract void addRawModification(RawModification rawModification);
 
   /**
    * Specifies the raw modifications for this modify operation.
    *
    * @param  rawModifications  The raw modifications for this modify operation.
    */
-  public final void setRawModifications(List<RawModification> rawModifications)
-  {
-    this.rawModifications = rawModifications;
-
-    modifications = null;
-  }
-
-
+  public abstract void setRawModifications(
+      List<RawModification> rawModifications);
 
   /**
    * Retrieves the set of modifications for this modify operation.  Its contents
@@ -350,12 +105,7 @@
    *          <CODE>null</CODE> if the modifications have not yet been
    *          processed.
    */
-  public final List<Modification> getModifications()
-  {
-    return modifications;
-  }
-
-
+  public abstract List<Modification> getModifications();
 
   /**
    * Adds the provided modification to the set of modifications to this modify
@@ -367,112 +117,8 @@
    * @throws  DirectoryException  If an unexpected problem occurs while applying
    *                              the modification to the entry.
    */
-  public final void addModification(Modification modification)
-         throws DirectoryException
-  {
-    modifiedEntry.applyModification(modification);
-    modifications.add(modification);
-  }
-
-
-
-  /**
-   * Retrieves the current entry before any modifications are applied.  This
-   * will not be available to pre-parse plugins.
-   *
-   * @return  The current entry, or <CODE>null</CODE> if it is not yet
-   *          available.
-   */
-  public final Entry getCurrentEntry()
-  {
-    return currentEntry;
-  }
-
-
-
-  /**
-   * Retrieves the modified entry that is to be written to the backend.  This
-   * will be available to pre-operation plugins, and if such a plugin does make
-   * a change to this entry, then it is also necessary to add that change to
-   * the set of modifications to ensure that the update will be consistent.
-   *
-   * @return  The modified entry that is to be written to the backend, or
-   *          <CODE>null</CODE> if it is not yet available.
-   */
-  public final Entry getModifiedEntry()
-  {
-    return modifiedEntry;
-  }
-
-
-
-  /**
-   * Retrieves the set of clear-text current passwords for the user, if
-   * available.  This will only be available if the modify operation contains
-   * one or more delete elements that target the password attribute and provide
-   * the values to delete in the clear.  It will not be available to pre-parse
-   * plugins.
-   *
-   * @return  The set of clear-text current password values as provided in the
-   *          modify request, or <CODE>null</CODE> if there were none or this
-   *          information is not yet available.
-   */
-  public final List<AttributeValue> getCurrentPasswords()
-  {
-    return currentPasswords;
-  }
-
-
-
-  /**
-   * Retrieves the set of clear-text new passwords for the user, if available.
-   * This will only be available if the modify operation contains one or more
-   * add or replace elements that target the password attribute and provide the
-   * values in the clear.  It will not be available to pre-parse plugins.
-   *
-   * @return  The set of clear-text new passwords as provided in the modify
-   *          request, or <CODE>null</CODE> if there were none or this
-   *          information is not yet available.
-   */
-  public final List<AttributeValue> getNewPasswords()
-  {
-    return newPasswords;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
+  public abstract void addModification(Modification modification)
+      throws DirectoryException;
 
   /**
    * Retrieves the change number that has been assigned to this operation.
@@ -481,12 +127,7 @@
    *          if none has been assigned yet or if there is no applicable
    *          synchronization mechanism in place that uses change numbers.
    */
-  public final long getChangeNumber()
-  {
-    return changeNumber;
-  }
-
-
+  public abstract long getChangeNumber();
 
   /**
    * Specifies the change number that has been assigned to this operation by the
@@ -495,131 +136,7 @@
    * @param  changeNumber  The change number that has been assigned to this
    *                       operation by the synchronization mechanism.
    */
-  public final void setChangeNumber(long changeNumber)
-  {
-    this.changeNumber = changeNumber;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void disconnectClient(DisconnectReason disconnectReason,
-                                     boolean sendNotification, String message,
-                                     int messageID)
-  {
-    // Before calling clientConnection.disconnect, we need to mark this
-    // operation as cancelled so that the attempt to cancel it later won't cause
-    // an unnecessary delay.
-    setCancelResult(CancelResult.CANCELED);
-
-    clientConnection.disconnect(disconnectReason, sendNotification, message,
-                                messageID);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final OperationType getOperationType()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    return OperationType.MODIFY;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final String[][] getRequestLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) }
-    };
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final String[][] getResponseLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    String resultCode = String.valueOf(getResultCode().getIntValue());
-
-    String errorMessage;
-    StringBuilder errorMessageBuffer = getErrorMessage();
-    if (errorMessageBuffer == null)
-    {
-      errorMessage = null;
-    }
-    else
-    {
-      errorMessage = errorMessageBuffer.toString();
-    }
-
-    String matchedDNStr;
-    DN matchedDN = getMatchedDN();
-    if (matchedDN == null)
-    {
-      matchedDNStr = null;
-    }
-    else
-    {
-      matchedDNStr = matchedDN.toString();
-    }
-
-    String referrals;
-    List<String> referralURLs = getReferralURLs();
-    if ((referralURLs == null) || referralURLs.isEmpty())
-    {
-      referrals = null;
-    }
-    else
-    {
-      StringBuilder buffer = new StringBuilder();
-      Iterator<String> iterator = referralURLs.iterator();
-      buffer.append(iterator.next());
-
-      while (iterator.hasNext())
-      {
-        buffer.append(", ");
-        buffer.append(iterator.next());
-      }
-
-      referrals = buffer.toString();
-    }
-
-    String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
-      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
-      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
-      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
-      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
-    };
-  }
-
-
+  public abstract void setChangeNumber(long changeNumber);
 
   /**
    * Retrieves the proxied authorization DN for this operation if proxied
@@ -629,2415 +146,17 @@
    *          authorization has been requested, or {@code null} if proxied
    *          authorization has not been requested.
    */
-  public DN getProxiedAuthorizationDN()
-  {
-    return proxiedAuthorizationDN;
-  }
-
-
+  public abstract DN getProxiedAuthorizationDN();
 
   /**
-   * {@inheritDoc}
+   * Set the proxied authorization DN for this operation if proxied
+   * authorization has been requested.
+   *
+   * @param proxiedAuthorizationDN
+   *          The proxied authorization DN for this operation if proxied
+   *          authorization has been requested, or {@code null} if proxied
+   *          authorization has not been requested.
    */
-  @Override()
-  public final List<Control> getResponseControls()
-  {
-    return responseControls;
-  }
+  public abstract void setProxiedAuthorizationDN(DN proxiedAuthorizationDN);
 
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void addResponseControl(Control control)
-  {
-    responseControls.add(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void removeResponseControl(Control control)
-  {
-    responseControls.remove(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void run()
-  {
-    setResultCode(ResultCode.UNDEFINED);
-
-
-    // Get the plugin config manager that will be used for invoking plugins.
-    PluginConfigManager pluginConfigManager =
-         DirectoryServer.getPluginConfigManager();
-    boolean skipPostOperation = false;
-
-
-    // Start the processing timer.
-    processingStartTime = System.currentTimeMillis();
-
-
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
-      return;
-    }
-
-
-    // Create a labeled block of code that we can break out of if a problem is
-    // detected.
-modifyProcessing:
-    {
-      // Invoke the pre-parse modify plugins.
-      PreParsePluginResult preParseResult =
-           pluginConfigManager.invokePreParseModifyPlugins(this);
-      if (preParseResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the request and
-        // result and return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-
-        logModifyRequest(this);
-        logModifyResponse(this);
-        pluginConfigManager.invokePostResponseModifyPlugins(this);
-        return;
-      }
-      else if (preParseResult.sendResponseImmediately())
-      {
-        skipPostOperation = true;
-        logModifyRequest(this);
-        break modifyProcessing;
-      }
-      else if (preParseResult.skipCoreProcessing())
-      {
-        skipPostOperation = false;
-        break modifyProcessing;
-      }
-
-
-      // Log the modify request message.
-      logModifyRequest(this);
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logModifyResponse(this);
-        pluginConfigManager.invokePostResponseModifyPlugins(this);
-        return;
-      }
-
-
-      // Process the entry DN to convert it from the raw form to the form
-      // required for the rest of the modify processing.
-      try
-      {
-        if (entryDN == null)
-        {
-          entryDN = DN.decode(rawEntryDN);
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        skipPostOperation = true;
-
-        break modifyProcessing;
-      }
-
-
-      // Process the modifications to convert them from their raw form to the
-      // form required for the rest of the modify processing.
-      if (modifications == null)
-      {
-        modifications = new ArrayList<Modification>(rawModifications.size());
-        for (RawModification m : rawModifications)
-        {
-          try
-          {
-            modifications.add(m.toModification());
-          }
-          catch (LDAPException le)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, le);
-            }
-
-            setResultCode(ResultCode.valueOf(le.getResultCode()));
-            appendErrorMessage(le.getMessage());
-
-            break modifyProcessing;
-          }
-        }
-      }
-
-      if (modifications.isEmpty())
-      {
-        setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-        appendErrorMessage(getMessage(MSGID_MODIFY_NO_MODIFICATIONS,
-                                      String.valueOf(entryDN)));
-        break modifyProcessing;
-      }
-
-
-      // If the user must change their password before doing anything else, and
-      // if the target of the modify operation isn't the user's own entry, then
-      // reject the request.
-      if ((! isInternalOperation()) && clientConnection.mustChangePassword())
-      {
-        DN authzDN = getAuthorizationDN();
-        if ((authzDN != null) && (! authzDN.equals(entryDN)))
-        {
-          // The user will not be allowed to do anything else before
-          // the password gets changed.
-          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-          int msgID = MSGID_MODIFY_MUST_CHANGE_PASSWORD;
-          appendErrorMessage(getMessage(msgID));
-          break modifyProcessing;
-        }
-      }
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logModifyResponse(this);
-        pluginConfigManager.invokePostResponseModifyPlugins(this);
-        return;
-      }
-
-
-      // Acquire a write lock on the target entry.
-      Lock entryLock = null;
-      for (int i=0; i < 3; i++)
-      {
-        entryLock = LockManager.lockWrite(entryDN);
-        if (entryLock != null)
-        {
-          break;
-        }
-      }
-
-      if (entryLock == null)
-      {
-        setResultCode(DirectoryServer.getServerErrorResultCode());
-        appendErrorMessage(getMessage(MSGID_MODIFY_CANNOT_LOCK_ENTRY,
-                                      String.valueOf(entryDN)));
-
-        skipPostOperation = true;
-        break modifyProcessing;
-      }
-
-
-      try
-      {
-        // Check for and handle a request to cancel this operation.
-        if (cancelRequest != null)
-        {
-          indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
-          logModifyResponse(this);
-          pluginConfigManager.invokePostResponseModifyPlugins(this);
-          return;
-        }
-
-
-        // Get the entry to modify.  If it does not exist, then fail.
-        try
-        {
-          currentEntry = DirectoryServer.getEntry(entryDN);
-        }
-        catch (DirectoryException de)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
-          }
-
-          setResultCode(de.getResultCode());
-          appendErrorMessage(de.getErrorMessage());
-          setMatchedDN(de.getMatchedDN());
-          setReferralURLs(de.getReferralURLs());
-
-          break modifyProcessing;
-        }
-
-        if (currentEntry == null)
-        {
-          setResultCode(ResultCode.NO_SUCH_OBJECT);
-          appendErrorMessage(getMessage(MSGID_MODIFY_NO_SUCH_ENTRY,
-                                        String.valueOf(entryDN)));
-
-          // See if one of the entry's ancestors exists.
-          DN parentDN = entryDN.getParentDNInSuffix();
-          while (parentDN != null)
-          {
-            try
-            {
-              if (DirectoryServer.entryExists(parentDN))
-              {
-                setMatchedDN(parentDN);
-                break;
-              }
-            }
-            catch (Exception e)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, e);
-              }
-              break;
-            }
-
-            parentDN = parentDN.getParentDNInSuffix();
-          }
-
-          break modifyProcessing;
-        }
-
-        // Check to see if there are any controls in the request.  If so, then
-        // see if there is any special processing required.
-        boolean                    noOp            = false;
-        LDAPPreReadRequestControl  preReadRequest  = null;
-        LDAPPostReadRequestControl postReadRequest = null;
-        List<Control> requestControls = getRequestControls();
-        if ((requestControls != null) && (! requestControls.isEmpty()))
-        {
-          for (int i=0; i < requestControls.size(); i++)
-          {
-            Control c   = requestControls.get(i);
-            String  oid = c.getOID();
-
-            if (oid.equals(OID_LDAP_ASSERTION))
-            {
-              LDAPAssertionRequestControl assertControl;
-              if (c instanceof LDAPAssertionRequestControl)
-              {
-                assertControl = (LDAPAssertionRequestControl) c;
-              }
-              else
-              {
-                try
-                {
-                  assertControl = LDAPAssertionRequestControl.decodeControl(c);
-                  requestControls.set(i, assertControl);
-                }
-                catch (LDAPException le)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break modifyProcessing;
-                }
-              }
-
-              try
-              {
-                // FIXME -- We need to determine whether the current user has
-                //          permission to make this determination.
-                SearchFilter filter = assertControl.getSearchFilter();
-                if (! filter.matchesEntry(currentEntry))
-                {
-                  setResultCode(ResultCode.ASSERTION_FAILED);
-
-                  appendErrorMessage(getMessage(MSGID_MODIFY_ASSERTION_FAILED,
-                                                String.valueOf(entryDN)));
-
-                  break modifyProcessing;
-                }
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(ResultCode.PROTOCOL_ERROR);
-
-                int msgID = MSGID_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              de.getErrorMessage()));
-
-                break modifyProcessing;
-              }
-            }
-            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);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break modifyProcessing;
-                }
-              }
-            }
-            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);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break modifyProcessing;
-                }
-              }
-            }
-            else if (oid.equals(OID_PROXIED_AUTH_V1))
-            {
-              // The requester must have the PROXIED_AUTH privilige in order to
-              // be able to use this control.
-              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-              {
-                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-                appendErrorMessage(getMessage(msgID));
-                setResultCode(ResultCode.AUTHORIZATION_DENIED);
-                break modifyProcessing;
-              }
-
-
-              ProxiedAuthV1Control proxyControl;
-              if (c instanceof ProxiedAuthV1Control)
-              {
-                proxyControl = (ProxiedAuthV1Control) c;
-              }
-              else
-              {
-                try
-                {
-                  proxyControl = ProxiedAuthV1Control.decodeControl(c);
-                }
-                catch (LDAPException le)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break modifyProcessing;
-                }
-              }
-
-
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
-
-                break modifyProcessing;
-              }
-              if (AccessControlConfigManager.getInstance().
-                      getAccessControlHandler().isProxiedAuthAllowed(this,
-                      authorizationEntry) == false) {
-                setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-                int msgID = MSGID_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-                skipPostOperation = true;
-                break modifyProcessing;
-              }
-
-              setAuthorizationEntry(authorizationEntry);
-              if (authorizationEntry == null)
-              {
-                proxiedAuthorizationDN = DN.nullDN();
-              }
-              else
-              {
-                proxiedAuthorizationDN = authorizationEntry.getDN();
-              }
-            }
-            else if (oid.equals(OID_PROXIED_AUTH_V2))
-            {
-              // The requester must have the PROXIED_AUTH privilige in order to
-              // be able to use this control.
-              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-              {
-                int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-                appendErrorMessage(getMessage(msgID));
-                setResultCode(ResultCode.AUTHORIZATION_DENIED);
-                break modifyProcessing;
-              }
-
-
-              ProxiedAuthV2Control proxyControl;
-              if (c instanceof ProxiedAuthV2Control)
-              {
-                proxyControl = (ProxiedAuthV2Control) c;
-              }
-              else
-              {
-                try
-                {
-                  proxyControl = ProxiedAuthV2Control.decodeControl(c);
-                }
-                catch (LDAPException le)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                  }
-
-                  setResultCode(ResultCode.valueOf(le.getResultCode()));
-                  appendErrorMessage(le.getMessage());
-
-                  break modifyProcessing;
-                }
-              }
-
-
-              Entry authorizationEntry;
-              try
-              {
-                authorizationEntry = proxyControl.getAuthorizationEntry();
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-                appendErrorMessage(de.getErrorMessage());
-
-                break modifyProcessing;
-              }
-
-              if (AccessControlConfigManager.getInstance().
-                      getAccessControlHandler().isProxiedAuthAllowed(this,
-                      authorizationEntry) == false) {
-                setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-                int msgID = MSGID_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-                skipPostOperation = true;
-                break modifyProcessing;
-              }
-              setAuthorizationEntry(authorizationEntry);
-              if (authorizationEntry == null)
-              {
-                proxiedAuthorizationDN = DN.nullDN();
-              }
-              else
-              {
-                proxiedAuthorizationDN = authorizationEntry.getDN();
-              }
-            }
-
-            // NYI -- Add support for additional controls.
-            else if (c.isCritical())
-            {
-              Backend backend = DirectoryServer.getBackend(entryDN);
-              if ((backend == null) || (! backend.supportsControl(oid)))
-              {
-                setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
-
-                int msgID = MSGID_MODIFY_UNSUPPORTED_CRITICAL_CONTROL;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              oid));
-
-                break modifyProcessing;
-              }
-            }
-          }
-        }
-
-
-        // Get the password policy state object for the entry that can be used
-        // to perform any appropriate password policy processing.  Also, see if
-        // the entry is being updated by the end user or an administrator.
-        PasswordPolicyState pwPolicyState;
-        boolean selfChange = entryDN.equals(getAuthorizationDN());
-        try
-        {
-          // FIXME -- Need a way to enable debug mode.
-          pwPolicyState = new PasswordPolicyState(currentEntry, false, false);
-        }
-        catch (DirectoryException de)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
-          }
-
-          setResultCode(de.getResultCode());
-          appendErrorMessage(de.getErrorMessage());
-
-          break modifyProcessing;
-        }
-
-
-        // Create a duplicate of the entry and apply the changes to it.
-        modifiedEntry = currentEntry.duplicate(false);
-
-        if (! noOp)
-        {
-          // Invoke any conflict resolution processing that might be needed by
-          // the synchronization provider.
-          for (SynchronizationProvider provider :
-               DirectoryServer.getSynchronizationProviders())
-          {
-            try
-            {
-              SynchronizationProviderResult result =
-                   provider.handleConflictResolution(this);
-              if (! result.continueOperationProcessing())
-              {
-                break modifyProcessing;
-              }
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              logError(ErrorLogCategory.SYNCHRONIZATION,
-                       ErrorLogSeverity.SEVERE_ERROR,
-                       MSGID_MODIFY_SYNCH_CONFLICT_RESOLUTION_FAILED,
-                       getConnectionID(), getOperationID(),
-                       getExceptionMessage(de));
-
-              setResponseData(de);
-              break modifyProcessing;
-            }
-          }
-        }
-
-
-        // Declare variables used for password policy state processing.
-        boolean passwordChanged = false;
-        boolean currentPasswordProvided = false;
-        boolean isEnabled = true;
-        boolean enabledStateChanged = false;
-        int numPasswords;
-        if (currentEntry.hasAttribute(
-                pwPolicyState.getPolicy().getPasswordAttribute()))
-        {
-          // It may actually have more than one, but we can't tell the
-          // difference if the values are encoded, and its enough for our
-          // purposes just to know that there is at least one.
-          numPasswords = 1;
-        }
-        else
-        {
-          numPasswords = 0;
-        }
-
-
-        // If it's not an internal or synchronization operation, then iterate
-        // through the set of modifications to see if a password is included in
-        // the changes.  If so, then add the appropriate state changes to the
-        // set of modifications.
-        if (! (isInternalOperation() || isSynchronizationOperation()))
-        {
-          for (Modification m : modifications)
-          {
-            if (m.getAttribute().getAttributeType().equals(
-                     pwPolicyState.getPolicy().getPasswordAttribute()))
-            {
-              passwordChanged = true;
-              if (! selfChange)
-              {
-                if (! clientConnection.hasPrivilege(Privilege.PASSWORD_RESET,
-                                                    this))
-                {
-                  int msgID = MSGID_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES;
-                  appendErrorMessage(getMessage(msgID));
-                  setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-                  break modifyProcessing;
-                }
-              }
-
-              break;
-            }
-          }
-        }
-
-
-        for (Modification m : modifications)
-        {
-          Attribute     a = m.getAttribute();
-          AttributeType t = a.getAttributeType();
-
-
-          // If the attribute type is marked "NO-USER-MODIFICATION" then fail
-          // unless this is an internal operation or is related to
-          // synchronization in some way.
-          if (t.isNoUserModification())
-          {
-            if (! (isInternalOperation() || isSynchronizationOperation() ||
-                   m.isInternal()))
-            {
-              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-              appendErrorMessage(getMessage(MSGID_MODIFY_ATTR_IS_NO_USER_MOD,
-                                            String.valueOf(entryDN),
-                                            a.getName()));
-              break modifyProcessing;
-            }
-          }
-
-          // If the attribute type is marked "OBSOLETE" and the modification
-          // is setting new values, then fail unless this is an internal
-          // operation or is related to synchronization in some way.
-          if (t.isObsolete())
-          {
-            if (a.hasValue() &&
-                (m.getModificationType() != ModificationType.DELETE))
-            {
-              if (! (isInternalOperation() || isSynchronizationOperation() ||
-                     m.isInternal()))
-              {
-                setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-                appendErrorMessage(getMessage(MSGID_MODIFY_ATTR_IS_OBSOLETE,
-                                              String.valueOf(entryDN),
-                                              a.getName()));
-                break modifyProcessing;
-              }
-            }
-          }
-
-
-          // See if the attribute is one which controls the privileges available
-          // for a user.  If it is, then the client must have the
-          // PRIVILEGE_CHANGE privilege.
-          if (t.hasName(OP_ATTR_PRIVILEGE_NAME))
-          {
-            if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE,
-                                                this))
-            {
-              int msgID = MSGID_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES;
-              appendErrorMessage(getMessage(msgID));
-              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-              break modifyProcessing;
-            }
-          }
-
-
-          // If the modification is updating the password attribute, then
-          // perform any necessary password policy processing.  This processing
-          // should be skipped for synchronization operations.
-          boolean isPassword
-                  = t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
-          if (isPassword && (!(isSynchronizationOperation())))
-          {
-           // If the attribute contains any options, then reject it.  Passwords
-           // will not be allowed to have options. Skipped for internal
-           // operations.
-           if(!isInternalOperation())
-           {
-            if (a.hasOptions())
-            {
-              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-              int msgID = MSGID_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS;
-              appendErrorMessage(getMessage(msgID));
-              break modifyProcessing;
-            }
-
-
-            // If it's a self change, then see if that's allowed.
-            if (selfChange &&
-                 (! pwPolicyState.getPolicy().allowUserPasswordChanges()))
-            {
-              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-              int msgID = MSGID_MODIFY_NO_USER_PW_CHANGES;
-              appendErrorMessage(getMessage(msgID));
-              break modifyProcessing;
-            }
-
-
-            // If we require secure password changes, then makes sure it's a
-            // secure communication channel.
-            if (pwPolicyState.getPolicy().requireSecurePasswordChanges() &&
-                (! clientConnection.isSecure()))
-            {
-              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-              int msgID = MSGID_MODIFY_REQUIRE_SECURE_CHANGES;
-              appendErrorMessage(getMessage(msgID));
-              break modifyProcessing;
-            }
-
-
-            // If it's a self change and it's not been long enough since the
-            // previous change, then reject it.
-            if (selfChange && pwPolicyState.isWithinMinimumAge())
-            {
-              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-              int msgID = MSGID_MODIFY_WITHIN_MINIMUM_AGE;
-              appendErrorMessage(getMessage(msgID));
-              break modifyProcessing;
-            }
-           }
-
-            // Check to see whether this will adding, deleting, or replacing
-            // password values (increment doesn't make any sense for passwords).
-            // Then perform the appropriate type of processing for that kind of
-            // modification.
-            boolean isAdd = false;
-            LinkedHashSet<AttributeValue> pwValues = a.getValues();
-            LinkedHashSet<AttributeValue> encodedValues =
-                 new LinkedHashSet<AttributeValue>();
-            switch (m.getModificationType())
-            {
-              case ADD:
-              case REPLACE:
-                int passwordsToAdd = pwValues.size();
-
-                if (m.getModificationType() == ModificationType.ADD)
-                {
-                  numPasswords += passwordsToAdd;
-                  isAdd = true;
-                }
-                else
-                {
-                  numPasswords = passwordsToAdd;
-                }
-                // If there were multiple password values provided, then make
-                // sure that's OK.
-
-                if (! isInternalOperation() &&
-                    ! pwPolicyState.getPolicy().allowExpiredPasswordChanges() &&
-                    (passwordsToAdd > 1))
-                {
-                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-                  int msgID = MSGID_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED;
-                  appendErrorMessage(getMessage(msgID));
-                  break modifyProcessing;
-                }
-
-                // Iterate through the password values and see if any of them
-                // are pre-encoded.  If so, then check to see if we'll allow it.
-                // Otherwise, store the clear-text values for later validation
-                // and update the attribute with the encoded values.
-                for (AttributeValue v : pwValues)
-                {
-                  if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
-                  {
-                    if ((!isInternalOperation()) &&
-                         ! pwPolicyState.getPolicy().allowPreEncodedPasswords())
-                    {
-                      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-                      int msgID = MSGID_MODIFY_NO_PREENCODED_PASSWORDS;
-                      appendErrorMessage(getMessage(msgID));
-                      break modifyProcessing;
-                    }
-                    else
-                    {
-                      encodedValues.add(v);
-                    }
-                  }
-                  else
-                  {
-                    if (isAdd)
-                    {
-                      // Make sure that the password value doesn't already
-                      // exist.
-                      if (pwPolicyState.passwordMatches(v.getValue()))
-                      {
-                        setResultCode(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS);
-
-                        int msgID = MSGID_MODIFY_PASSWORD_EXISTS;
-                        appendErrorMessage(getMessage(msgID));
-                        break modifyProcessing;
-                      }
-                    }
-
-                    if (newPasswords == null)
-                    {
-                      newPasswords = new LinkedList<AttributeValue>();
-                    }
-
-                    newPasswords.add(v);
-
-                    try
-                    {
-                      for (ByteString s :
-                           pwPolicyState.encodePassword(v.getValue()))
-                      {
-                        encodedValues.add(new AttributeValue(
-                                                   a.getAttributeType(), s));
-                      }
-                    }
-                    catch (DirectoryException de)
-                    {
-                      if (debugEnabled())
-                      {
-                        TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                      }
-
-                      setResultCode(de.getResultCode());
-                      appendErrorMessage(de.getErrorMessage());
-                      break modifyProcessing;
-                    }
-                  }
-                }
-
-                a.setValues(encodedValues);
-
-                break;
-
-              case DELETE:
-                // Iterate through the password values and see if any of them
-                // are pre-encoded.  We will never allow pre-encoded passwords
-                // for user password changes, but we will allow them for
-                // administrators.  For each clear-text value, verify that at
-                // least one value in the entry matches and replace the
-                // clear-text value with the appropriate encoded forms.
-                for (AttributeValue v : pwValues)
-                {
-                  if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
-                  {
-                    if ((!isInternalOperation()) && selfChange)
-                    {
-                      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-                      int msgID = MSGID_MODIFY_NO_PREENCODED_PASSWORDS;
-                      appendErrorMessage(getMessage(msgID));
-                      break modifyProcessing;
-                    }
-                    else
-                    {
-                      encodedValues.add(v);
-                    }
-                  }
-                  else
-                  {
-                    List<Attribute> attrList = currentEntry.getAttribute(t);
-                    if ((attrList == null) || (attrList.isEmpty()))
-                    {
-                      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-                      int msgID = MSGID_MODIFY_NO_EXISTING_VALUES;
-                      appendErrorMessage(getMessage(msgID));
-                      break modifyProcessing;
-                    }
-                    boolean found = false;
-                    for (Attribute attr : attrList)
-                    {
-                      for (AttributeValue av : attr.getValues())
-                      {
-                        if (pwPolicyState.getPolicy().usesAuthPasswordSyntax())
-                        {
-                          if (AuthPasswordSyntax.isEncoded(av.getValue()))
-                          {
-                            try
-                            {
-                              StringBuilder[] compoenents =
-                                   AuthPasswordSyntax.decodeAuthPassword(
-                                        av.getStringValue());
-                              PasswordStorageScheme scheme =
-                                   DirectoryServer.
-                                        getAuthPasswordStorageScheme(
-                                             compoenents[0].toString());
-                              if (scheme != null)
-                              {
-                                if (scheme.authPasswordMatches(
-                                     v.getValue(),
-                                     compoenents[1].toString(),
-                                     compoenents[2].toString()))
-                                {
-                                  encodedValues.add(av);
-                                  found = true;
-                                }
-                              }
-                            }
-                            catch (DirectoryException de)
-                            {
-                              if (debugEnabled())
-                              {
-                                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                              }
-
-                              setResultCode(de.getResultCode());
-
-                              int msgID = MSGID_MODIFY_CANNOT_DECODE_PW;
-                              appendErrorMessage(
-                                   getMessage(msgID, de.getErrorMessage()));
-                              break modifyProcessing;
-                            }
-                          }
-                          else
-                          {
-                            if (av.equals(v))
-                            {
-                              encodedValues.add(v);
-                              found = true;
-                            }
-                          }
-                        }
-                        else
-                        {
-                          if (UserPasswordSyntax.isEncoded(av.getValue()))
-                          {
-                            try
-                            {
-                              String[] compoenents =
-                                   UserPasswordSyntax.decodeUserPassword(
-                                        av.getStringValue());
-                              PasswordStorageScheme scheme =
-                                   DirectoryServer.getPasswordStorageScheme(
-                                        toLowerCase(compoenents[0]));
-                              if (scheme != null)
-                              {
-                                if (scheme.passwordMatches(
-                                     v.getValue(),
-                                     new ASN1OctetString(compoenents[1])))
-                                {
-                                  encodedValues.add(av);
-                                  found = true;
-                                }
-                              }
-                            }
-                            catch (DirectoryException de)
-                            {
-                              if (debugEnabled())
-                              {
-                                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                              }
-
-                              setResultCode(de.getResultCode());
-
-                              int msgID = MSGID_MODIFY_CANNOT_DECODE_PW;
-                              appendErrorMessage(getMessage(msgID,
-                                                         de.getErrorMessage()));
-                              break modifyProcessing;
-                            }
-                          }
-                          else
-                          {
-                            if (av.equals(v))
-                            {
-                              encodedValues.add(v);
-                              found = true;
-                            }
-                          }
-                        }
-                      }
-                    }
-
-                    if (found)
-                    {
-                      if (currentPasswords == null)
-                      {
-                        currentPasswords = new LinkedList<AttributeValue>();
-                      }
-                      currentPasswords.add(v);
-
-                      numPasswords--;
-                    }
-                    else
-                    {
-                      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-                      int msgID = MSGID_MODIFY_INVALID_PASSWORD;
-                      appendErrorMessage(getMessage(msgID));
-                      break modifyProcessing;
-                    }
-
-                    currentPasswordProvided = true;
-                  }
-                }
-
-                a.setValues(encodedValues);
-
-                break;
-
-              default:
-                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-                int msgID = MSGID_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD;
-                appendErrorMessage(getMessage(msgID,
-                     String.valueOf(m.getModificationType()), a.getName()));
-
-                break modifyProcessing;
-            }
-          }
-          else
-          {
-            // See if it's an attribute used to maintain the account
-            // enabled/disabled state.
-            AttributeType disabledAttr =
-                 DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED,
-                                                  true);
-            if (t.equals(disabledAttr))
-            {
-              enabledStateChanged = true;
-              for (AttributeValue v : a.getValues())
-              {
-                try
-                {
-                  isEnabled = (! BooleanSyntax.decodeBooleanValue(
-                                                    v.getNormalizedValue()));
-                }
-                catch (DirectoryException de)
-                {
-                  setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-
-                  int msgID = MSGID_MODIFY_INVALID_DISABLED_VALUE;
-                  String message =
-                       getMessage(msgID, OP_ATTR_ACCOUNT_DISABLED,
-                                  String.valueOf(de.getErrorMessage()));
-                  appendErrorMessage(message);
-                  break modifyProcessing;
-                }
-              }
-            }
-          }
-
-
-          switch (m.getModificationType())
-          {
-            case ADD:
-              // Make sure that one or more values have been provided for the
-              // attribute.
-              LinkedHashSet<AttributeValue> newValues = a.getValues();
-              if ((newValues == null) || newValues.isEmpty())
-              {
-                setResultCode(ResultCode.PROTOCOL_ERROR);
-                appendErrorMessage(getMessage(MSGID_MODIFY_ADD_NO_VALUES,
-                                              String.valueOf(entryDN),
-                                              a.getName()));
-                break modifyProcessing;
-              }
-
-
-              // Make sure that all the new values are valid according to the
-              // associated syntax.
-              if (DirectoryServer.checkSchema())
-              {
-                AcceptRejectWarn syntaxPolicy =
-                     DirectoryServer.getSyntaxEnforcementPolicy();
-                AttributeSyntax syntax = t.getSyntax();
-
-                if (syntaxPolicy == AcceptRejectWarn.REJECT)
-                {
-                  StringBuilder invalidReason = new StringBuilder();
-
-                  for (AttributeValue v : newValues)
-                  {
-                    if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
-                    {
-                      setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-
-                      int msgID = MSGID_MODIFY_ADD_INVALID_SYNTAX;
-                      appendErrorMessage(getMessage(msgID,
-                                                    String.valueOf(entryDN),
-                                                    a.getName(),
-                                                    v.getStringValue(),
-                                                    invalidReason.toString()));
-
-                      break modifyProcessing;
-                    }
-                  }
-                }
-                else if (syntaxPolicy == AcceptRejectWarn.WARN)
-                {
-                  StringBuilder invalidReason = new StringBuilder();
-
-                  for (AttributeValue v : newValues)
-                  {
-                    if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
-                    {
-                      setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-
-                      int msgID = MSGID_MODIFY_ADD_INVALID_SYNTAX;
-                      logError(ErrorLogCategory.SCHEMA,
-                               ErrorLogSeverity.SEVERE_WARNING, msgID,
-                               String.valueOf(entryDN), a.getName(),
-                               v.getStringValue(), invalidReason.toString());
-
-                      invalidReason = new StringBuilder();
-                    }
-                  }
-                }
-              }
-
-
-              // Add the provided attribute or merge an existing attribute with
-              // the values of the new attribute.  If there are any duplicates,
-              // then fail.
-              LinkedList<AttributeValue> duplicateValues =
-                   new LinkedList<AttributeValue>();
-              if (a.getAttributeType().isObjectClassType())
-              {
-                try
-                {
-                  modifiedEntry.addObjectClasses(newValues);
-                  break;
-                }
-                catch (DirectoryException de)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                  }
-
-                  setResponseData(de);
-                  break modifyProcessing;
-                }
-              }
-              else
-              {
-                modifiedEntry.addAttribute(a, duplicateValues);
-                if (duplicateValues.isEmpty())
-                {
-                  break;
-                }
-                else
-                {
-                  StringBuilder buffer = new StringBuilder();
-                  Iterator<AttributeValue> iterator =
-                       duplicateValues.iterator();
-                  buffer.append(iterator.next().getStringValue());
-                  while (iterator.hasNext())
-                  {
-                    buffer.append(", ");
-                    buffer.append(iterator.next().getStringValue());
-                  }
-
-                  setResultCode(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS);
-
-                  int msgID = MSGID_MODIFY_ADD_DUPLICATE_VALUE;
-                  appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                                a.getName(),
-                                                buffer.toString()));
-
-                  break modifyProcessing;
-                }
-              }
-
-
-            case DELETE:
-              // Remove the specified attribute values or the entire attribute
-              // from the value.  If there are any specified values that were
-              // not present, then fail.  If the RDN attribute value would be
-              // removed, then fail.
-              LinkedList<AttributeValue> missingValues =
-                   new LinkedList<AttributeValue>();
-              boolean attrExists =
-                   modifiedEntry.removeAttribute(a, missingValues);
-
-              if (attrExists)
-              {
-                if (missingValues.isEmpty())
-                {
-                  RDN rdn = modifiedEntry.getDN().getRDN();
-                  if ((rdn != null) && rdn.hasAttributeType(t) &&
-                      (! modifiedEntry.hasValue(t, a.getOptions(),
-                                                rdn.getAttributeValue(t))))
-                  {
-                    setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
-
-                    int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
-                    appendErrorMessage(getMessage(msgID,
-                                                  String.valueOf(entryDN),
-                                                  a.getName()));
-                    break modifyProcessing;
-                  }
-
-                  break;
-                }
-                else
-                {
-                  StringBuilder buffer = new StringBuilder();
-                  Iterator<AttributeValue> iterator = missingValues.iterator();
-                  buffer.append(iterator.next().getStringValue());
-                  while (iterator.hasNext())
-                  {
-                    buffer.append(", ");
-                    buffer.append(iterator.next().getStringValue());
-                  }
-
-                  setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
-
-                  int msgID = MSGID_MODIFY_DELETE_MISSING_VALUES;
-                  appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                                a.getName(),
-                                                buffer.toString()));
-
-                  break modifyProcessing;
-                }
-              }
-              else
-              {
-                setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
-
-                int msgID = MSGID_MODIFY_DELETE_NO_SUCH_ATTR;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              a.getName()));
-                break modifyProcessing;
-              }
-
-
-            case REPLACE:
-              // If it is the objectclass attribute, then treat that separately.
-              if (a.getAttributeType().isObjectClassType())
-              {
-                try
-                {
-                  modifiedEntry.setObjectClasses(a.getValues());
-                  break;
-                }
-                catch (DirectoryException de)
-                {
-                  if (debugEnabled())
-                  {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                  }
-
-                  setResponseData(de);
-                  break modifyProcessing;
-                }
-              }
-
-
-              // If the provided attribute does not have any values, then we
-              // will simply remove the attribute from the entry (if it exists).
-              if (! a.hasValue())
-              {
-                modifiedEntry.removeAttribute(t, a.getOptions());
-                RDN rdn = modifiedEntry.getDN().getRDN();
-                if ((rdn != null) && rdn.hasAttributeType(t) &&
-                    (! modifiedEntry.hasValue(t, a.getOptions(),
-                                              rdn.getAttributeValue(t))))
-                {
-                  setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
-
-                  int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
-                  appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                                a.getName()));
-                  break modifyProcessing;
-                }
-                break;
-              }
-
-
-              // Make sure that all the new values are valid according to the
-              // associated syntax.
-              newValues = a.getValues();
-              if (DirectoryServer.checkSchema())
-              {
-                AcceptRejectWarn syntaxPolicy =
-                     DirectoryServer.getSyntaxEnforcementPolicy();
-                AttributeSyntax syntax = t.getSyntax();
-
-                if (syntaxPolicy == AcceptRejectWarn.REJECT)
-                {
-                  StringBuilder invalidReason = new StringBuilder();
-
-                  for (AttributeValue v : newValues)
-                  {
-                    if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
-                    {
-                      setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-
-                      int msgID = MSGID_MODIFY_REPLACE_INVALID_SYNTAX;
-                      appendErrorMessage(getMessage(msgID,
-                                                    String.valueOf(entryDN),
-                                                    a.getName(),
-                                                    v.getStringValue(),
-                                                    invalidReason.toString()));
-
-                      break modifyProcessing;
-                    }
-                  }
-                }
-                else if (syntaxPolicy == AcceptRejectWarn.WARN)
-                {
-                  StringBuilder invalidReason = new StringBuilder();
-
-                  for (AttributeValue v : newValues)
-                  {
-                    if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
-                    {
-                      setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-
-                      int msgID = MSGID_MODIFY_REPLACE_INVALID_SYNTAX;
-                      logError(ErrorLogCategory.SCHEMA,
-                               ErrorLogSeverity.SEVERE_WARNING, msgID,
-                               String.valueOf(entryDN), a.getName(),
-                               v.getStringValue(), invalidReason.toString());
-
-                      invalidReason = new StringBuilder();
-                    }
-                  }
-                }
-              }
-
-
-              // If the provided attribute does not have any options, then we
-              // will simply use it in place of any existing attribute of the
-              // provided type (or add it if it doesn't exist).
-              if (! a.hasOptions())
-              {
-                List<Attribute> attrList = new ArrayList<Attribute>(1);
-                attrList.add(a);
-                modifiedEntry.putAttribute(t, attrList);
-
-                RDN rdn = modifiedEntry.getDN().getRDN();
-                if ((rdn != null) && rdn.hasAttributeType(t) &&
-                    (! modifiedEntry.hasValue(t, a.getOptions(),
-                                              rdn.getAttributeValue(t))))
-                {
-                  setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
-
-                  int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
-                  appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                                a.getName()));
-                  break modifyProcessing;
-                }
-                break;
-              }
-
-
-              // See if there is an existing attribute of the provided type.  If
-              // not, then we'll use the new one.
-              List<Attribute> attrList = modifiedEntry.getAttribute(t);
-              if ((attrList == null) || attrList.isEmpty())
-              {
-                attrList = new ArrayList<Attribute>(1);
-                attrList.add(a);
-                modifiedEntry.putAttribute(t, attrList);
-
-                RDN rdn = modifiedEntry.getDN().getRDN();
-                if ((rdn != null) && rdn.hasAttributeType(t) &&
-                    (! modifiedEntry.hasValue(t, a.getOptions(),
-                                              rdn.getAttributeValue(t))))
-                {
-                  setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
-
-                  int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
-                  appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                                a.getName()));
-                  break modifyProcessing;
-                }
-                break;
-              }
-
-
-              // There must be an existing occurrence of the provided attribute
-              // in the entry.  If there is a version with exactly the set of
-              // options provided, then replace it.  Otherwise, add a new one.
-              boolean found = false;
-              for (int i=0; i < attrList.size(); i++)
-              {
-                if (attrList.get(i).optionsEqual(a.getOptions()))
-                {
-                  attrList.set(i, a);
-                  found = true;
-                  break;
-                }
-              }
-
-              if (! found)
-              {
-                attrList.add(a);
-              }
-
-              RDN rdn = modifiedEntry.getDN().getRDN();
-              if ((rdn != null) && rdn.hasAttributeType(t) &&
-                  (! modifiedEntry.hasValue(t, a.getOptions(),
-                                            rdn.getAttributeValue(t))))
-              {
-                setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
-
-                int msgID = MSGID_MODIFY_DELETE_RDN_ATTR;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              a.getName()));
-                break modifyProcessing;
-              }
-              break;
-
-
-            case INCREMENT:
-              // The specified attribute type must not be an RDN attribute.
-              rdn = modifiedEntry.getDN().getRDN();
-              if ((rdn != null) && rdn.hasAttributeType(t))
-              {
-                setResultCode(ResultCode.NOT_ALLOWED_ON_RDN);
-                appendErrorMessage(getMessage(MSGID_MODIFY_INCREMENT_RDN,
-                                              String.valueOf(entryDN),
-                                              a.getName()));
-              }
-
-
-              // The provided attribute must have a single value, and it must be
-              // an integer.
-              LinkedHashSet<AttributeValue> values = a.getValues();
-              if ((values == null) || values.isEmpty())
-              {
-                setResultCode(ResultCode.PROTOCOL_ERROR);
-
-                int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_VALUE;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              a.getName()));
-
-                break modifyProcessing;
-              }
-              else if (values.size() > 1)
-              {
-                setResultCode(ResultCode.PROTOCOL_ERROR);
-
-                int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_SINGLE_VALUE;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              a.getName()));
-                break modifyProcessing;
-              }
-
-              AttributeValue v = values.iterator().next();
-
-              long incrementValue;
-              try
-              {
-                incrementValue = Long.parseLong(v.getNormalizedStringValue());
-              }
-              catch (Exception e)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                }
-
-                setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-
-                int msgID = MSGID_MODIFY_INCREMENT_PROVIDED_VALUE_NOT_INTEGER;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              a.getName(), v.getStringValue()));
-
-                break modifyProcessing;
-              }
-
-
-              // Get the corresponding attribute from the entry and make sure
-              // that it has a single integer value.
-              attrList = modifiedEntry.getAttribute(t, a.getOptions());
-              if ((attrList == null) || attrList.isEmpty())
-              {
-                setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-
-                int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              a.getName()));
-
-                break modifyProcessing;
-              }
-
-              boolean updated = false;
-              for (Attribute attr : attrList)
-              {
-                LinkedHashSet<AttributeValue> valueList = attr.getValues();
-                if ((valueList == null) || valueList.isEmpty())
-                {
-                  continue;
-                }
-
-                LinkedHashSet<AttributeValue> newValueList =
-                     new LinkedHashSet<AttributeValue>(valueList.size());
-                for (AttributeValue existingValue : valueList)
-                {
-                  long newIntValue;
-                  try
-                  {
-                    long existingIntValue =
-                         Long.parseLong(existingValue.getStringValue());
-                    newIntValue = existingIntValue + incrementValue;
-                  }
-                  catch (Exception e)
-                  {
-                    if (debugEnabled())
-                    {
-                      TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                    }
-
-                    setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
-
-                    int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE;
-                    appendErrorMessage(getMessage(msgID,
-                                            String.valueOf(entryDN),
-                                            a.getName(),
-                                            existingValue.getStringValue()));
-                    break modifyProcessing;
-                  }
-
-                  ByteString newValue =
-                       new ASN1OctetString(String.valueOf(newIntValue));
-                  newValueList.add(new AttributeValue(t, newValue));
-                }
-
-                attr.setValues(newValueList);
-                updated = true;
-              }
-
-              if (! updated)
-              {
-                setResultCode(ResultCode.CONSTRAINT_VIOLATION);
-
-                int msgID = MSGID_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE;
-                appendErrorMessage(getMessage(msgID, String.valueOf(entryDN),
-                                              a.getName()));
-
-                break modifyProcessing;
-              }
-
-              break;
-
-            default:
-          }
-        }
-
-
-        // If there was a password change, then perform any additional checks
-        // that may be necessary.
-        if (passwordChanged)
-        {
-          // If it was a self change, then see if the current password was
-          // provided and handle accordingly.
-          if (selfChange &&
-              pwPolicyState.getPolicy().requireCurrentPassword() &&
-              (! currentPasswordProvided))
-          {
-            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-            int msgID = MSGID_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW;
-            appendErrorMessage(getMessage(msgID));
-            break modifyProcessing;
-          }
-
-
-          // If this change would result in multiple password values, then see
-          // if that's OK.
-          if ((numPasswords > 1) &&
-              (! pwPolicyState.getPolicy().allowMultiplePasswordValues()))
-          {
-            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-            int msgID = MSGID_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED;
-            appendErrorMessage(getMessage(msgID));
-            break modifyProcessing;
-          }
-
-
-          // If any of the password values should be validated, then do so now.
-          if (selfChange ||
-               (! pwPolicyState.getPolicy().skipValidationForAdministrators()))
-          {
-            if (newPasswords != null)
-            {
-              HashSet<ByteString> clearPasswords = new HashSet<ByteString>();
-              clearPasswords.addAll(pwPolicyState.getClearPasswords());
-
-              if (currentPasswords != null)
-              {
-                if (clearPasswords.isEmpty())
-                {
-                  for (AttributeValue v : currentPasswords)
-                  {
-                    clearPasswords.add(v.getValue());
-                  }
-                }
-                else
-                {
-                  // NOTE:  We can't rely on the fact that Set doesn't allow
-                  // duplicates because technically it's possible that the
-                  // values aren't duplicates if they are ASN.1 elements with
-                  // different types (like 0x04 for a standard universal octet
-                  // string type versus 0x80 for a simple password in a bind
-                  // operation).  So we have to manually check for duplicates.
-                  for (AttributeValue v : currentPasswords)
-                  {
-                    ByteString pw = v.getValue();
-
-                    boolean found = false;
-                    for (ByteString s : clearPasswords)
-                    {
-                      if (Arrays.equals(s.value(), pw.value()))
-                      {
-                        found = true;
-                        break;
-                      }
-                    }
-
-                    if (! found)
-                    {
-                      clearPasswords.add(pw);
-                    }
-                  }
-                }
-              }
-
-              for (AttributeValue v : newPasswords)
-              {
-                StringBuilder invalidReason = new StringBuilder();
-                if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
-                                                         v.getValue(),
-                                                         clearPasswords,
-                                                         invalidReason))
-                {
-                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-                  int msgID = MSGID_MODIFY_PW_VALIDATION_FAILED;
-                  appendErrorMessage(getMessage(msgID,
-                                                invalidReason.toString()));
-                  break modifyProcessing;
-                }
-              }
-            }
-          }
-        }
-
-
-        // Check to see if the client has permission to perform the
-        // modify.
-        // The access control check is not made any earlier because the
-        // handler needs access to the modified entry.
-
-        // FIXME: for now assume that this will check all permission
-        // pertinent to the operation. This includes proxy authorization
-        // and any other controls specified.
-
-        // FIXME: earlier checks to see if the entry already exists may
-        // have already exposed sensitive information to the client.
-        if (!AccessControlConfigManager.getInstance()
-             .getAccessControlHandler().isAllowed(this)) {
-          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-          int msgID = MSGID_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-          appendErrorMessage(getMessage(msgID, String.valueOf(entryDN)));
-
-          skipPostOperation = true;
-          break modifyProcessing;
-        }
-
-        boolean wasLocked = false;
-        if (passwordChanged)
-        {
-          // See if the account was locked for any reason.
-          wasLocked = pwPolicyState.lockedDueToIdleInterval() ||
-               pwPolicyState.lockedDueToMaximumResetAge() ||
-               pwPolicyState.lockedDueToFailures();
-
-          // Update the password policy state attributes in the user's entry.
-          // If the modification fails, then these changes won't be applied.
-          pwPolicyState.setPasswordChangedTime();
-          pwPolicyState.clearFailureLockout();
-          pwPolicyState.clearGraceLoginTimes();
-          pwPolicyState.clearWarnedTime();
-
-          if(pwPolicyState.getPolicy().forceChangeOnAdd()
-             || pwPolicyState.getPolicy().forceChangeOnReset())
-          {
-            if (selfChange)
-            {
-              pwPolicyState.setMustChangePassword(false);
-            }
-            else
-            {
-              pwPolicyState.setMustChangePassword(
-                   pwPolicyState.getPolicy().forceChangeOnReset());
-            }
-          }
-
-          if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
-          {
-            pwPolicyState.setRequiredChangeTime();
-          }
-          modifications.addAll(pwPolicyState.getModifications());
-          //Apply pwd Policy modifications to modified entry.
-          try {
-            modifiedEntry.applyModifications(pwPolicyState.getModifications());
-          } catch (DirectoryException e) {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, e);
-            }
-
-            setResponseData(e);
-            break modifyProcessing;
-          }
-        }
-        else if ((! isInternalOperation()) &&
-                 pwPolicyState.mustChangePassword())
-        {
-          // The user will not be allowed to do anything else before
-          // the password gets changed.
-          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-
-          int msgID = MSGID_MODIFY_MUST_CHANGE_PASSWORD;
-          appendErrorMessage(getMessage(msgID));
-          break modifyProcessing;
-        }
-
-        // Make sure that the new entry is valid per the server schema.
-        if (DirectoryServer.checkSchema())
-        {
-          StringBuilder invalidReason = new StringBuilder();
-          if (! modifiedEntry.conformsToSchema(null, false, false, false,
-                                               invalidReason))
-          {
-            setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
-            appendErrorMessage(getMessage(MSGID_MODIFY_VIOLATES_SCHEMA,
-                                          String.valueOf(entryDN),
-                                          invalidReason.toString()));
-            break modifyProcessing;
-          }
-        }
-
-        // Check for and handle a request to cancel this operation.
-        if (cancelRequest != null)
-        {
-          indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
-          logModifyResponse(this);
-          pluginConfigManager.invokePostResponseModifyPlugins(this);
-          return;
-        }
-
-        // If the operation is not a synchronization operation,
-        // Invoke the pre-operation modify plugins.
-        if (!isSynchronizationOperation())
-        {
-          PreOperationPluginResult preOpResult =
-            pluginConfigManager.invokePreOperationModifyPlugins(this);
-          if (preOpResult.connectionTerminated())
-          {
-            // There's no point in continuing with anything.  Log the result
-            // and return.
-            setResultCode(ResultCode.CANCELED);
-
-            int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-            appendErrorMessage(getMessage(msgID));
-
-            processingStopTime = System.currentTimeMillis();
-
-            logModifyResponse(this);
-            pluginConfigManager.invokePostResponseModifyPlugins(this);
-            return;
-          }
-          else if (preOpResult.sendResponseImmediately())
-          {
-            skipPostOperation = true;
-            break modifyProcessing;
-          }
-          else if (preOpResult.skipCoreProcessing())
-          {
-            skipPostOperation = false;
-            break modifyProcessing;
-          }
-        }
-
-
-        // Check for and handle a request to cancel this operation.
-        if (cancelRequest != null)
-        {
-          indicateCancelled(cancelRequest);
-          processingStopTime = System.currentTimeMillis();
-          logModifyResponse(this);
-          pluginConfigManager.invokePostResponseModifyPlugins(this);
-          return;
-        }
-
-
-        // Actually perform the modify operation.  This should also include
-        // taking care of any synchronization that might be needed.
-        Backend backend = DirectoryServer.getBackend(entryDN);
-        if (backend == null)
-        {
-          setResultCode(ResultCode.NO_SUCH_OBJECT);
-          appendErrorMessage(getMessage(MSGID_MODIFY_NO_BACKEND_FOR_ENTRY,
-                                        String.valueOf(entryDN)));
-          break modifyProcessing;
-        }
-
-        try
-        {
-          // If it is not a private backend, then check to see if the server or
-          // backend is operating in read-only mode.
-          if (! backend.isPrivateBackend())
-          {
-            switch (DirectoryServer.getWritabilityMode())
-            {
-              case DISABLED:
-                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                appendErrorMessage(getMessage(MSGID_MODIFY_SERVER_READONLY,
-                                              String.valueOf(entryDN)));
-                break modifyProcessing;
-
-              case INTERNAL_ONLY:
-                if (! (isInternalOperation() || isSynchronizationOperation()))
-                {
-                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                  appendErrorMessage(getMessage(MSGID_MODIFY_SERVER_READONLY,
-                                                String.valueOf(entryDN)));
-                  break modifyProcessing;
-                }
-            }
-
-            switch (backend.getWritabilityMode())
-            {
-              case DISABLED:
-                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                appendErrorMessage(getMessage(MSGID_MODIFY_BACKEND_READONLY,
-                                              String.valueOf(entryDN)));
-                break modifyProcessing;
-
-              case INTERNAL_ONLY:
-                if (! (isInternalOperation() || isSynchronizationOperation()))
-                {
-                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
-                  appendErrorMessage(getMessage(MSGID_MODIFY_BACKEND_READONLY,
-                                                String.valueOf(entryDN)));
-                  break modifyProcessing;
-                }
-            }
-          }
-
-
-          if (noOp)
-          {
-            appendErrorMessage(getMessage(MSGID_MODIFY_NOOP));
-
-            // FIXME -- We must set a result code other than SUCCESS.
-          }
-          else
-          {
-            for (SynchronizationProvider provider :
-                 DirectoryServer.getSynchronizationProviders())
-            {
-              try
-              {
-                SynchronizationProviderResult result =
-                     provider.doPreOperation(this);
-                if (! result.continueOperationProcessing())
-                {
-                  break modifyProcessing;
-                }
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                logError(ErrorLogCategory.SYNCHRONIZATION,
-                         ErrorLogSeverity.SEVERE_ERROR,
-                         MSGID_MODIFY_SYNCH_PREOP_FAILED, getConnectionID(),
-                         getOperationID(), getExceptionMessage(de));
-
-                setResponseData(de);
-                break modifyProcessing;
-              }
-            }
-
-            backend.replaceEntry(modifiedEntry, this);
-
-
-            // If the modification was successful, then see if there's any other
-            // work that we need to do here before handing off to postop
-            // plugins.
-            if (passwordChanged)
-            {
-              if (selfChange)
-              {
-                AuthenticationInfo authInfo =
-                     clientConnection.getAuthenticationInfo();
-                if (authInfo.getAuthenticationDN().equals(entryDN))
-                {
-                  clientConnection.setMustChangePassword(false);
-                }
-
-                int    msgID   = MSGID_MODIFY_PASSWORD_CHANGED;
-                String message = getMessage(msgID);
-                pwPolicyState.generateAccountStatusNotification(
-                     AccountStatusNotificationType.PASSWORD_CHANGED, entryDN,
-                     msgID, message);
-              }
-              else
-              {
-                int    msgID   = MSGID_MODIFY_PASSWORD_RESET;
-                String message = getMessage(msgID);
-                pwPolicyState.generateAccountStatusNotification(
-                     AccountStatusNotificationType.PASSWORD_RESET, entryDN,
-                     msgID, message);
-              }
-            }
-
-            if (enabledStateChanged)
-            {
-              if (isEnabled)
-              {
-                int    msgID   = MSGID_MODIFY_ACCOUNT_ENABLED;
-                String message = getMessage(msgID);
-                pwPolicyState.generateAccountStatusNotification(
-                     AccountStatusNotificationType.ACCOUNT_ENABLED, entryDN,
-                     msgID, message);
-              }
-              else
-              {
-                int    msgID   = MSGID_MODIFY_ACCOUNT_DISABLED;
-                String message = getMessage(msgID);
-                pwPolicyState.generateAccountStatusNotification(
-                     AccountStatusNotificationType.ACCOUNT_DISABLED, entryDN,
-                     msgID, message);
-              }
-            }
-
-            if (wasLocked)
-            {
-              int    msgID   = MSGID_MODIFY_ACCOUNT_UNLOCKED;
-              String message = getMessage(msgID);
-              pwPolicyState.generateAccountStatusNotification(
-                   AccountStatusNotificationType.ACCOUNT_UNLOCKED, entryDN,
-                   msgID, message);
-            }
-          }
-
-          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);
-
-            responseControls.add(responseControl);
-          }
-
-          if (postReadRequest != null)
-          {
-            Entry entry = modifiedEntry.duplicate(true);
-
-            if (! postReadRequest.allowsAttribute(
-                       DirectoryServer.getObjectClassAttributeType()))
-            {
-              entry.removeAttribute(
-                   DirectoryServer.getObjectClassAttributeType());
-            }
-
-            if (! postReadRequest.returnAllUserAttributes())
-            {
-              Iterator<AttributeType> iterator =
-                   entry.getUserAttributes().keySet().iterator();
-              while (iterator.hasNext())
-              {
-                AttributeType attrType = iterator.next();
-                if (! postReadRequest.allowsAttribute(attrType))
-                {
-                  iterator.remove();
-                }
-              }
-            }
-
-            if (! postReadRequest.returnAllOperationalAttributes())
-            {
-              Iterator<AttributeType> iterator =
-                   entry.getOperationalAttributes().keySet().iterator();
-              while (iterator.hasNext())
-              {
-                AttributeType attrType = iterator.next();
-                if (! postReadRequest.allowsAttribute(attrType))
-                {
-                  iterator.remove();
-                }
-              }
-            }
-
-            // FIXME -- Check access controls on the entry to see if it should
-            //          be returned or if any attributes need to be stripped
-            //          out..
-            SearchResultEntry searchEntry = new SearchResultEntry(entry);
-            LDAPPostReadResponseControl responseControl =
-                 new LDAPPostReadResponseControl(postReadRequest.getOID(),
-                                                 postReadRequest.isCritical(),
-                                                 searchEntry);
-
-            responseControls.add(responseControl);
-          }
-
-          setResultCode(ResultCode.SUCCESS);
-        }
-        catch (DirectoryException de)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
-          }
-
-          setResultCode(de.getResultCode());
-          appendErrorMessage(de.getErrorMessage());
-          setMatchedDN(de.getMatchedDN());
-          setReferralURLs(de.getReferralURLs());
-
-          break modifyProcessing;
-        }
-        catch (CancelledOperationException coe)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
-          }
-
-          CancelResult cancelResult = coe.getCancelResult();
-
-          setCancelResult(cancelResult);
-          setResultCode(cancelResult.getResultCode());
-
-          String message = coe.getMessage();
-          if ((message != null) && (message.length() > 0))
-          {
-            appendErrorMessage(message);
-          }
-
-          break modifyProcessing;
-        }
-      }
-      finally
-      {
-        LockManager.unlock(entryDN, entryLock);
-
-        for (SynchronizationProvider provider :
-             DirectoryServer.getSynchronizationProviders())
-        {
-          try
-          {
-            provider.doPostOperation(this);
-          }
-          catch (DirectoryException de)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, de);
-            }
-
-            logError(ErrorLogCategory.SYNCHRONIZATION,
-                     ErrorLogSeverity.SEVERE_ERROR,
-                     MSGID_MODIFY_SYNCH_POSTOP_FAILED, 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 modify plugins.
-    if (! skipPostOperation)
-    {
-      // FIXME -- Should this also be done while holding the locks?
-      PostOperationPluginResult postOpResult =
-           pluginConfigManager.invokePostOperationModifyPlugins(this);
-      if (postOpResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the result and
-        // return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-
-        logModifyResponse(this);
-        pluginConfigManager.invokePostResponseModifyPlugins(this);
-        return;
-      }
-    }
-
-
-    // Notify any change notification listeners that might be registered with
-    // the server.
-    if (getResultCode() == ResultCode.SUCCESS)
-    {
-      for (ChangeNotificationListener changeListener :
-           DirectoryServer.getChangeNotificationListeners())
-      {
-        try
-        {
-          changeListener.handleModifyOperation(this, currentEntry,
-                                               modifiedEntry);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER;
-          String message = getMessage(msgID, getExceptionMessage(e));
-          logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
-                   message, msgID);
-        }
-      }
-    }
-
-
-    // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
-
-
-    // Send the modify response to the client.
-    clientConnection.sendResponse(this);
-
-
-    // Log the modify response.
-    logModifyResponse(this);
-
-
-    // Notify any persistent searches that might be registered with the server.
-    if (getResultCode() == ResultCode.SUCCESS)
-    {
-      for (PersistentSearch persistentSearch :
-           DirectoryServer.getPersistentSearches())
-      {
-        try
-        {
-          persistentSearch.processModify(this, currentEntry, modifiedEntry);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_MODIFY_ERROR_NOTIFYING_PERSISTENT_SEARCH;
-          String message = getMessage(msgID, String.valueOf(persistentSearch),
-                                      getExceptionMessage(e));
-          logError(ErrorLogCategory.CORE_SERVER, ErrorLogSeverity.SEVERE_ERROR,
-                   message, msgID);
-
-          DirectoryServer.deregisterPersistentSearch(persistentSearch);
-        }
-      }
-    }
-
-
-    // Invoke the post-response modify plugins.
-    pluginConfigManager.invokePostResponseModifyPlugins(this);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelResult cancel(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-
-    CancelResult cancelResult = getCancelResult();
-    long stopWaitingTime = System.currentTimeMillis() + 5000;
-    while ((cancelResult == null) &&
-           (System.currentTimeMillis() < stopWaitingTime))
-    {
-      try
-      {
-        Thread.sleep(50);
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-      }
-
-      cancelResult = getCancelResult();
-    }
-
-    if (cancelResult == null)
-    {
-      // This can happen in some rare cases (e.g., if a client disconnects and
-      // there is still a lot of data to send to that client), and in this case
-      // we'll prevent the cancel thread from blocking for a long period of
-      // time.
-      cancelResult = CancelResult.CANNOT_CANCEL;
-    }
-
-    return cancelResult;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelRequest getCancelRequest()
-  {
-    return cancelRequest;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-    return true;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void toString(StringBuilder buffer)
-  {
-    buffer.append("ModifyOperation(connID=");
-    buffer.append(clientConnection.getConnectionID());
-    buffer.append(", opID=");
-    buffer.append(operationID);
-    buffer.append(", dn=");
-    buffer.append(rawEntryDN);
-    buffer.append(")");
-  }
-}
-
+}
\ No newline at end of file
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperationBasis.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperationBasis.java
new file mode 100644
index 0000000..f02b61f
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperationBasis.java
@@ -0,0 +1,773 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ENTRY_DN;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ERROR_MESSAGE;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_MATCHED_DN;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_PROCESSING_TIME;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_REFERRAL_URLS;
+import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
+import static org.opends.server.loggers.AccessLogger.logModifyRequest;
+import static org.opends.server.loggers.AccessLogger.logModifyResponse;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.messages.CoreMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.plugin.PreParsePluginResult;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.ldap.LDAPAttribute;
+import org.opends.server.types.LDAPException;
+import org.opends.server.protocols.ldap.LDAPModification;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.Operation;
+import org.opends.server.types.RawModification;
+import org.opends.server.types.AbstractOperation;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Modification;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.operation.PostResponseModifyOperation;
+import org.opends.server.types.operation.PreParseModifyOperation;
+import org.opends.server.workflowelement.localbackend.*;
+
+
+
+/**
+ * This class defines an operation that may be used to modify an entry in the
+ * Directory Server.
+ */
+public class ModifyOperationBasis
+       extends AbstractOperation implements ModifyOperation,
+       PreParseModifyOperation,
+       PostResponseModifyOperation
+       {
+
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
+
+  // The raw, unprocessed entry DN as included by the client request.
+  private ByteString rawEntryDN;
+
+  // The DN of the entry for the modify operation.
+  private DN entryDN;
+
+  // The proxied authorization target DN for this operation.
+  private DN proxiedAuthorizationDN;
+
+  // The set of response controls for this modify operation.
+  private List<Control> responseControls;
+
+  // The raw, unprocessed set of modifications as included in the client
+  // request.
+  private List<RawModification> rawModifications;
+
+  // The set of modifications for this modify operation.
+  private List<Modification> modifications;
+
+  // The cancel request that has been issued for this modify operation.
+  CancelRequest cancelRequest;
+
+  // The change number that has been assigned to this operation.
+  private long changeNumber;
+
+  /**
+   * Creates a new modify operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  rawEntryDN        The raw, unprocessed DN of the entry to modify,
+   *                           as included in the client request.
+   * @param  rawModifications  The raw, unprocessed set of modifications for
+   *                           this modify operation as included in the client
+   *                           request.
+   */
+  public ModifyOperationBasis(ClientConnection clientConnection,
+      long operationID,
+      int messageID, List<Control> requestControls,
+      ByteString rawEntryDN,
+      List<RawModification> rawModifications)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.rawEntryDN       = rawEntryDN;
+    this.rawModifications = rawModifications;
+
+    entryDN          = null;
+    modifications    = null;
+    responseControls = new ArrayList<Control>();
+    cancelRequest    = null;
+  }
+
+  /**
+   * Creates a new modify operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  entryDN           The entry DN for the modify operation.
+   * @param  modifications     The set of modifications for this modify
+   *                           operation.
+   */
+  public ModifyOperationBasis(ClientConnection clientConnection,
+      long operationID,
+      int messageID, List<Control> requestControls,
+      DN entryDN, List<Modification> modifications)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.entryDN       = entryDN;
+    this.modifications = modifications;
+
+    rawEntryDN = new ASN1OctetString(entryDN.toString());
+
+    rawModifications = new ArrayList<RawModification>(modifications.size());
+    for (Modification m : modifications)
+    {
+      rawModifications.add(new LDAPModification(m.getModificationType(),
+          new LDAPAttribute(m.getAttribute())));
+    }
+
+    responseControls = new ArrayList<Control>();
+    cancelRequest    = null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ByteString getRawEntryDN()
+  {
+    return rawEntryDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawEntryDN(ByteString rawEntryDN)
+  {
+    this.rawEntryDN = rawEntryDN;
+
+    entryDN = null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DN getEntryDN()
+  {
+    if (entryDN == null){
+      try {
+        entryDN = DN.decode(rawEntryDN);
+      }
+      catch (DirectoryException de) {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+
+        setResultCode(de.getResultCode());
+        appendErrorMessage(de.getErrorMessage());
+      }
+    }
+    return entryDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final List<RawModification> getRawModifications()
+  {
+    return rawModifications;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void addRawModification(RawModification rawModification)
+  {
+    rawModifications.add(rawModification);
+
+    modifications = null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawModifications(List<RawModification> rawModifications)
+  {
+    this.rawModifications = rawModifications;
+
+    modifications = null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final List<Modification> getModifications()
+  {
+    if (modifications == null)
+    {
+      modifications = new ArrayList<Modification>(rawModifications.size());
+      try {
+        for (RawModification m : rawModifications)
+        {
+          modifications.add(m.toModification());
+        }
+      }
+      catch (LDAPException le)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, le);
+        }
+        setResultCode(ResultCode.valueOf(le.getResultCode()));
+        appendErrorMessage(le.getMessage());
+        modifications = null;
+      }
+    }
+    return modifications;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void addModification(Modification modification)
+  throws DirectoryException
+  {
+    modifications.add(modification);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void disconnectClient(DisconnectReason disconnectReason,
+      boolean sendNotification, String message,
+      int messageID)
+  {
+    // Before calling clientConnection.disconnect, we need to mark this
+    // operation as cancelled so that the attempt to cancel it later won't cause
+    // an unnecessary delay.
+    setCancelResult(CancelResult.CANCELED);
+
+    clientConnection.disconnect(disconnectReason, sendNotification, message,
+        messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final OperationType getOperationType()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    return OperationType.MODIFY;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String[][] getRequestLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    return new String[][]
+                        {
+        new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) }
+                        };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String[][] getResponseLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    String resultCode = String.valueOf(getResultCode().getIntValue());
+
+    String errorMessage;
+    StringBuilder errorMessageBuffer = getErrorMessage();
+    if (errorMessageBuffer == null)
+    {
+      errorMessage = null;
+    }
+    else
+    {
+      errorMessage = errorMessageBuffer.toString();
+    }
+
+    String matchedDNStr;
+    DN matchedDN = getMatchedDN();
+    if (matchedDN == null)
+    {
+      matchedDNStr = null;
+    }
+    else
+    {
+      matchedDNStr = matchedDN.toString();
+    }
+
+    String referrals;
+    List<String> referralURLs = getReferralURLs();
+    if ((referralURLs == null) || referralURLs.isEmpty())
+    {
+      referrals = null;
+    }
+    else
+    {
+      StringBuilder buffer = new StringBuilder();
+      Iterator<String> iterator = referralURLs.iterator();
+      buffer.append(iterator.next());
+
+      while (iterator.hasNext())
+      {
+        buffer.append(", ");
+        buffer.append(iterator.next());
+      }
+
+      referrals = buffer.toString();
+    }
+
+    String processingTime =
+      String.valueOf(getProcessingTime());
+
+    return new String[][]
+                        {
+        new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
+        new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
+        new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
+        new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
+        new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
+                        };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return proxiedAuthorizationDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final List<Control> getResponseControls()
+  {
+    return responseControls;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void addResponseControl(Control control)
+  {
+    responseControls.add(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void removeResponseControl(Control control)
+  {
+    responseControls.remove(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final CancelResult cancel(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+
+    CancelResult cancelResult = getCancelResult();
+    long stopWaitingTime = System.currentTimeMillis() + 5000;
+    while ((cancelResult == null) &&
+        (System.currentTimeMillis() < stopWaitingTime))
+    {
+      try
+      {
+        Thread.sleep(50);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+      }
+
+      cancelResult = getCancelResult();
+    }
+
+    if (cancelResult == null)
+    {
+      // This can happen in some rare cases (e.g., if a client disconnects and
+      // there is still a lot of data to send to that client), and in this case
+      // we'll prevent the cancel thread from blocking for a long period of
+      // time.
+      cancelResult = CancelResult.CANNOT_CANCEL;
+    }
+
+    return cancelResult;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final CancelRequest getCancelRequest()
+  {
+    return cancelRequest;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+    return true;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void toString(StringBuilder buffer)
+  {
+    buffer.append("ModifyOperation(connID=");
+    buffer.append(clientConnection.getConnectionID());
+    buffer.append(", opID=");
+    buffer.append(operationID);
+    buffer.append(", dn=");
+    buffer.append(rawEntryDN);
+    buffer.append(")");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getChangeNumber(){
+    return changeNumber;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setChangeNumber(long changeNumber)
+  {
+    this.changeNumber = changeNumber;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
+  {
+    this.proxiedAuthorizationDN = proxiedAuthorizationDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void run()
+  {
+    setResultCode(ResultCode.UNDEFINED);
+
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+         DirectoryServer.getPluginConfigManager();
+
+    // Start the processing timer.
+    setProcessingStartTime();
+
+    // Check for and handle a request to cancel this operation.
+    if (getCancelRequest() != null)
+    {
+      indicateCancelled(getCancelRequest());
+      setProcessingStopTime();
+      return;
+    }
+
+
+    // Create a labeled block of code that we can break out of if a problem is
+    // detected.
+modifyProcessing:
+    {
+      // Invoke the pre-parse modify plugins.
+      PreParsePluginResult preParseResult =
+           pluginConfigManager.invokePreParseModifyPlugins(this);
+      if (preParseResult.connectionTerminated())
+      {
+        // There's no point in continuing with anything.  Log the request and
+        // result and return.
+        setResultCode(ResultCode.CANCELED);
+
+        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
+        appendErrorMessage(getMessage(msgID));
+
+        setProcessingStopTime();
+
+        logModifyRequest(this);
+        logModifyResponse(this);
+        pluginConfigManager.invokePostResponseModifyPlugins(this);
+        return;
+      }
+      else if (preParseResult.sendResponseImmediately())
+      {
+        logModifyRequest(this);
+        break modifyProcessing;
+      }
+      else if (preParseResult.skipCoreProcessing())
+      {
+        break modifyProcessing;
+      }
+
+      // Log the modify request message.
+      logModifyRequest(this);
+
+      // Check for and handle a request to cancel this operation.
+      if (getCancelRequest() != null)
+      {
+        indicateCancelled(getCancelRequest());
+        setProcessingStopTime();
+        pluginConfigManager.invokePostResponseModifyPlugins(this);
+        return;
+      }
+
+
+      // Process the entry DN to convert it from the raw form to the form
+      // required for the rest of the modify processing.
+      DN entryDN = getEntryDN();
+      if (entryDN == null){
+        break modifyProcessing;
+      }
+
+
+      // Retrieve the network group attached to the client connection
+      // and get a workflow to process the operation.
+      NetworkGroup ng = getClientConnection().getNetworkGroup();
+      Workflow workflow = ng.getWorkflowCandidate(entryDN);
+      if (workflow == null)
+      {
+        // We have found no workflow for the requested base DN, just return
+        // a no such entry result code and stop the processing.
+        updateOperationErrMsgAndResCode();
+        break modifyProcessing;
+      }
+      workflow.execute(this);
+    }
+
+    // Check for and handle a request to cancel this operation.
+    if ((getCancelRequest() != null) ||
+        (getCancelResult() == CancelResult.CANCELED))
+    {
+      if (getCancelRequest() != null){
+        indicateCancelled(getCancelRequest());
+      }
+      setProcessingStopTime();
+      logModifyResponse(this);
+      invokePostResponsePlugins();
+      return;
+    }
+
+    // Indicate that it is now too late to attempt to cancel the operation.
+    setCancelResult(CancelResult.TOO_LATE);
+
+    // -- DONE AT A LOWER LEVEL --
+    // Notify any change notification listeners that might be registered with
+    // the server.
+
+    // Stop the processing timer.
+    setProcessingStopTime();
+
+    // Send the modify response to the client.
+    getClientConnection().sendResponse(this);
+
+    // Log the modify response.
+    logModifyResponse(this);
+
+    // Check wether there are local operations in attachments
+    List localOperations =
+      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
+    if (localOperations != null && (! localOperations.isEmpty())){
+      for (Object localOp : localOperations)
+      {
+        LocalBackendModifyOperation localOperation =
+          (LocalBackendModifyOperation)localOp;
+        // Notify any persistent searches that might be registered with
+        // the server.
+        if (localOperation.getResultCode() == ResultCode.SUCCESS)
+        {
+          for (PersistentSearch persistentSearch :
+               DirectoryServer.getPersistentSearches())
+          {
+            try
+            {
+              persistentSearch.processModify(localOperation,
+                  localOperation.getCurrentEntry(),
+                  localOperation.getModifiedEntry());
+            }
+            catch (Exception e)
+            {
+              if (debugEnabled())
+              {
+                TRACER.debugCaught(DebugLogLevel.ERROR, e);
+              }
+
+              int    msgID   = MSGID_MODIFY_ERROR_NOTIFYING_PERSISTENT_SEARCH;
+              String message = getMessage(msgID,
+                                          String.valueOf(persistentSearch),
+                                          getExceptionMessage(e));
+              logError(ErrorLogCategory.CORE_SERVER,
+                       ErrorLogSeverity.SEVERE_ERROR,
+                       message, msgID);
+
+              DirectoryServer.deregisterPersistentSearch(persistentSearch);
+            }
+          }
+        }
+
+        // Invoke the post-response modify plugins.
+        pluginConfigManager.invokePostResponseModifyPlugins(localOperation);
+      }
+    }
+  }
+
+  /**
+   * Updates the error message and the result code of the operation.
+   *
+   * This method is called because no workflows were found to process
+   * the operation.
+   */
+  private void updateOperationErrMsgAndResCode()
+  {
+    setResultCode(ResultCode.NO_SUCH_OBJECT);
+    appendErrorMessage(getMessage(MSGID_MODIFY_NO_SUCH_ENTRY,
+                                  String.valueOf(getEntryDN())));
+  }
+
+  /**
+   * Execute the postResponseModifyPlugins.
+   */
+  private void invokePostResponsePlugins()
+  {
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+      DirectoryServer.getPluginConfigManager();
+
+    // Check wether there are local operations in attachments
+    List localOperations =
+      (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
+
+    if (localOperations != null && (! localOperations.isEmpty())){
+      for (Object localOp : localOperations)
+      {
+        LocalBackendModifyOperation localOperation =
+          (LocalBackendModifyOperation)localOp;
+        // Invoke the post-response add plugins.
+        pluginConfigManager.invokePostResponseModifyPlugins(localOperation);
+      }
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * This method always returns null.
+   */
+  public Entry getCurrentEntry() {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * This method always returns null.
+   */
+  public List<AttributeValue> getCurrentPasswords()
+  {
+    return null;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * This method always returns null.
+   */
+  public Entry getModifiedEntry()
+  {
+    return null;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * This method always returns null.
+   */
+  public List<AttributeValue> getNewPasswords()
+  {
+    return null;
+  }
+
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperationWrapper.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperationWrapper.java
new file mode 100644
index 0000000..46968f3
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperationWrapper.java
@@ -0,0 +1,622 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+import java.util.List;
+import java.util.Map;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.RawModification;
+import org.opends.server.types.ResultCode;
+
+/**
+ * This abstract class wraps/decorates a given modify operation.
+ * This class will be extended by sub-classes to enhance the
+ * functionnality of the ModifyOperationBasis.
+ */
+public abstract class ModifyOperationWrapper implements ModifyOperation
+{
+  private ModifyOperation modify;
+
+  /**
+   * Creates a new modify operation based on the provided modify operation.
+   *
+   * @param modify The modify operation to wrap
+   */
+  protected ModifyOperationWrapper(ModifyOperation modify){
+    this.modify = modify;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addModification(Modification modification)
+    throws DirectoryException
+  {
+    modify.addModification(modification);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addRawModification(RawModification rawModification)
+  {
+    modify.addRawModification(rawModification);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addResponseControl(Control control)
+  {
+    modify.addResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult cancel(CancelRequest cancelRequest)
+  {
+    return modify.cancel(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void disconnectClient(DisconnectReason disconnectReason,
+      boolean sendNotification, String message, int messageID)
+  {
+    modify.disconnectClient(disconnectReason, sendNotification,
+        message, messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean dontSynchronize()
+  {
+    return modify.dontSynchronize();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(Object obj)
+  {
+    return modify.equals(obj);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelRequest getCancelRequest()
+  {
+    return modify.getCancelRequest();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getEntryDN()
+  {
+    return modify.getEntryDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Modification> getModifications()
+  {
+    return modify.getModifications();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public OperationType getOperationType()
+  {
+    return modify.getOperationType();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStartTime()
+  {
+    return modify.getProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStopTime()
+  {
+    return modify.getProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingTime()
+  {
+    return modify.getProcessingTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getRawEntryDN()
+  {
+    return modify.getRawEntryDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<RawModification> getRawModifications()
+  {
+    return modify.getRawModifications();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getRequestLogElements()
+  {
+    return modify.getRequestLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getResponseControls()
+  {
+    return modify.getResponseControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getResponseLogElements()
+  {
+    return modify.getResponseLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int hashCode()
+  {
+    return modify.hashCode();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeResponseControl(Control control)
+  {
+    modify.removeResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean setCancelRequest(CancelRequest cancelRequest){
+    return modify.setCancelRequest(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawEntryDN(ByteString rawEntryDN)
+  {
+    modify.setRawEntryDN(rawEntryDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawModifications(List<RawModification> rawModifications)
+  {
+    modify.setRawModifications(rawModifications);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void toString(StringBuilder buffer)
+  {
+    modify.toString(buffer);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStopTime(){
+    modify.setProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStartTime(){
+    modify.setProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addRequestControl(Control control)
+  {
+    modify.addRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendAdditionalLogMessage(String message)
+  {
+    modify.appendAdditionalLogMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendErrorMessage(String message)
+  {
+    modify.appendErrorMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getAdditionalLogMessage()
+  {
+    return modify.getAdditionalLogMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object getAttachment(String name)
+  {
+    return modify.getAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<String, Object> getAttachments()
+  {
+    return modify.getAttachments();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getAuthorizationDN()
+  {
+    return modify.getAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry getAuthorizationEntry()
+  {
+    return modify.getAuthorizationEntry();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult getCancelResult()
+  {
+    return modify.getCancelResult();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ClientConnection getClientConnection()
+  {
+    return modify.getClientConnection();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getCommonLogElements()
+  {
+    return modify.getCommonLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getConnectionID()
+  {
+    return modify.getConnectionID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getErrorMessage()
+  {
+    return modify.getErrorMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getMatchedDN()
+  {
+    return modify.getMatchedDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getMessageID()
+  {
+    return modify.getMessageID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getOperationID()
+  {
+    return modify.getOperationID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<String> getReferralURLs()
+  {
+    return modify.getReferralURLs();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getRequestControls()
+  {
+    return modify.getRequestControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ResultCode getResultCode()
+  {
+    return modify.getResultCode();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void indicateCancelled(CancelRequest cancelRequest)
+  {
+    modify.indicateCancelled(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isInternalOperation()
+  {
+    return modify.isInternalOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isSynchronizationOperation()
+  {
+    return modify.isSynchronizationOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void operationCompleted()
+  {
+    modify.operationCompleted();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object removeAttachment(String name)
+  {
+    return modify.removeAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeRequestControl(Control control)
+  {
+    modify.removeRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAdditionalLogMessage(StringBuilder additionalLogMessage)
+  {
+    modify.setAdditionalLogMessage(additionalLogMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object setAttachment(String name, Object value)
+  {
+    return modify.setAttachment(name, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttachments(Map<String, Object> attachments)
+  {
+    modify.setAttachments(attachments);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAuthorizationEntry(Entry authorizationEntry)
+  {
+    modify.setAuthorizationEntry(authorizationEntry);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setCancelResult(CancelResult cancelResult)
+  {
+    modify.setCancelResult(cancelResult);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setDontSynchronize(boolean dontSynchronize)
+  {
+    modify.setDontSynchronize(dontSynchronize);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setErrorMessage(StringBuilder errorMessage)
+  {
+    modify.setErrorMessage(errorMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setInternalOperation(boolean isInternalOperation)
+  {
+    modify.setInternalOperation(isInternalOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setMatchedDN(DN matchedDN)
+  {
+    modify.setMatchedDN(matchedDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setReferralURLs(List<String> referralURLs)
+  {
+    modify.setReferralURLs(referralURLs);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResponseData(DirectoryException directoryException)
+  {
+    modify.setResponseData(directoryException);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResultCode(ResultCode resultCode)
+  {
+    modify.setResultCode(resultCode);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSynchronizationOperation(boolean isSynchronizationOperation)
+  {
+    modify.setSynchronizationOperation(isSynchronizationOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return modify.toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getChangeNumber(){
+    return modify.getChangeNumber();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setChangeNumber(long changeNumber)
+  {
+    modify.setChangeNumber(changeNumber);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return modify.getProxiedAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN){
+    modify.setProxiedAuthorizationDN(proxiedAuthorizationDN);
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroup.java b/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroup.java
new file mode 100644
index 0000000..516e24d
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroup.java
@@ -0,0 +1,407 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import java.util.ArrayList;
+
+import org.opends.server.types.DN;
+import org.opends.server.workflowelement.WorkflowElement;
+
+
+/**
+ * This class defines the network group. A network group is used to categorize
+ * client connections. A network group is defined by a set of criteria, a
+ * set of policies and a set of workflows. A client connection belongs to a
+ * network group whenever it satisfies all the network group criteria. As soon
+ * as a client connection belongs to a network group, it has to comply with
+ * all the network group policies and all the client operation can be routed
+ * to one the network group workflows.
+ */
+public class NetworkGroup
+{
+  // Workflows registered with the current network group.
+  private ArrayList<WorkflowTopologyNode> registeredWorkflows =
+      new ArrayList<WorkflowTopologyNode>();
+
+
+  // The workflow for the rootDSE entry. The RootDSE workflow
+  // is not stored in the list of registered workflows.
+  private RootDseWorkflowTopology rootDSEWorkflow = null;
+
+
+  // List of naming contexts handled by the network group.
+  private NetworkGroupNamingContexts namingContexts =
+      new NetworkGroupNamingContexts();
+
+
+  // The default network group (singleton).
+  // The default network group has no criterion, no policy, and gives
+  // access to all the workflows. The purpose of the default network
+  // group is to allow new clients to perform a first operation before
+  // they can be attached to a specific network group.
+  private static NetworkGroup defaultNetworkGroup =
+      new NetworkGroup ("default");
+
+
+  // The list of all network groups that have been created.
+  // The default network group is not stored in that pool.
+  private static ArrayList<NetworkGroup> networkGroupPool =
+      new ArrayList<NetworkGroup>();
+
+
+  // Human readable network group name.
+  private String networkGroupName = null;
+
+
+  /**
+   * Creates a new instance of the network group.
+   *
+   * @param networkGroupName  the name of the network group for debug purpose
+   */
+  public NetworkGroup (
+      String networkGroupName
+      )
+  {
+    this.networkGroupName = networkGroupName;
+  }
+
+
+  /**
+   * Registers a network group with the pool of network groups.
+   *
+   * @param networkGroup  the network group to register
+   */
+  public static void registerNetworkGroup(
+      NetworkGroup networkGroup
+      )
+  {
+    networkGroupPool.add(networkGroup);
+  }
+
+
+  /**
+   * Registers a workflow with the network group.
+   *
+   * @param workflow  the workflow to register
+   */
+  public void registerWorkflow(
+      WorkflowImpl workflow
+      )
+  {
+    // The workflow is rgistered with no pre/post workflow element.
+    registerWorkflow(workflow, null, null);
+  }
+
+
+  /**
+   * Registers a workflow with the network group and the workflow may have
+   * pre and post workflow element.
+   *
+   * @param workflow              the workflow to register
+   * @param preWorkflowElements   the tasks to execute before the workflow
+   * @param postWorkflowElements  the tasks to execute after the workflow
+   */
+  private void registerWorkflow(
+      WorkflowImpl workflow,
+      WorkflowElement[] preWorkflowElements,
+      WorkflowElement[] postWorkflowElements
+      )
+  {
+    // true as soon as the workflow has been registered
+    boolean registered = false;
+
+    // Is it the rootDSE workflow?
+    DN baseDN = workflow.getBaseDN();
+    if (baseDN.isNullDN())
+    {
+      // NOTE - The rootDSE workflow is not stored in the pool.
+      rootDSEWorkflow = new RootDseWorkflowTopology(workflow, namingContexts);
+      registered = true;
+    }
+    else
+    {
+      // This workflow is not the rootDSE workflow. Try to insert it in the
+      // workflow topology.
+      if (! baseDNAlreadyRegistered(baseDN))
+      {
+        WorkflowTopologyNode workflowTopology = new WorkflowTopologyNode(
+            workflow, preWorkflowElements, postWorkflowElements);
+
+        // Add the workflow in the workflow topology...
+        for (WorkflowTopologyNode curWorkflow: registeredWorkflows)
+        {
+          // Try to insert the new workflow under an existing workflow...
+          if (curWorkflow.insertSubordinate(workflowTopology))
+          {
+            // new workflow has been inserted in the topology
+            break;
+          }
+
+          // ... or try to insert the existing workflow below the new
+          // workflow
+          if (workflowTopology.insertSubordinate(curWorkflow))
+          {
+            // new workflow has been inserted in the topology
+            break;
+          }
+        }
+
+        // ... then register the workflow with the pool.
+        registeredWorkflows.add(workflowTopology);
+        registered = true;
+
+        // Rebuild the list of naming context handled by the network group
+        rebuildNamingContextList();
+      }
+    }
+
+    // If the workflow has been registered successfully then register it
+    // with the default network group
+    if (registered)
+    {
+      if (this != defaultNetworkGroup)
+      {
+        defaultNetworkGroup.registerWorkflow(
+            workflow, preWorkflowElements, postWorkflowElements);
+      }
+    }
+  }
+
+
+  /**
+   * Deregisters a workflow with the network group.
+   *
+   * @param baseDN  the baseDN of the workflow to deregister
+   */
+  public void deregisterWorkflow (
+      DN baseDN
+      )
+  {
+    Workflow workflow = getWorkflowCandidate(baseDN);
+    if (workflow != null)
+    {
+      deregisterWorkflow(workflow);
+    }
+  }
+
+
+  /**
+   * Deregisters a workflow with the network group.
+   *
+   * @param workflow  the workflow to deregister
+   */
+  private void deregisterWorkflow(
+      Workflow workflow
+      )
+  {
+    // true as soon as the workflow has been deregistered
+    boolean deregistered = false;
+
+    // Is it the rootDSE workflow?
+    if (workflow == rootDSEWorkflow)
+    {
+      rootDSEWorkflow = null;
+      deregistered = true;
+    }
+    else
+    {
+      // The workflow to deregister is not the root DSE workflow.
+      // Remove it from the workflow topology.
+      WorkflowTopologyNode workflowTopology = (WorkflowTopologyNode) workflow;
+      workflowTopology.remove();
+
+      // Then deregister the workflow with the network group.
+      registeredWorkflows.remove(workflow);
+      deregistered = true;
+
+      // Rebuild the list of naming context handled by the network group
+      rebuildNamingContextList();
+    }
+
+    // If the workflow has been deregistered then deregister it with
+    // the default network group as well
+    if (deregistered)
+    {
+      if (this != defaultNetworkGroup)
+      {
+        defaultNetworkGroup.deregisterWorkflow(workflow);
+      }
+    }
+  }
+
+
+  /**
+   * Gets the highest workflow in the topology that can handle the baseDN.
+   *
+   * @param baseDN  the base DN of the request
+   * @return the highest workflow in the topology that can handle the base DN,
+   *         <code>null</code> if none was found
+   */
+  public Workflow getWorkflowCandidate (
+      DN baseDN
+      )
+  {
+    // the top workflow to return
+    Workflow workflowCandidate = null;
+
+    // get the list of workflow candidates
+    if (baseDN.isNullDN())
+    {
+      // The rootDSE workflow is the candidate.
+      workflowCandidate = rootDSEWorkflow;
+    }
+    else
+    {
+      // Search the highest workflow in the topology that can handle
+      // the baseDN.
+      for (WorkflowTopologyNode curWorkflow: namingContexts.getNamingContexts())
+      {
+        workflowCandidate = curWorkflow.getWorkflowCandidate (baseDN);
+        if (workflowCandidate != null)
+        {
+          break;
+        }
+      }
+    }
+
+    return workflowCandidate;
+  }
+
+
+  /**
+   * Returns the default network group. The default network group is always
+   * defined and has no criterion, no policy and provide full access to
+   * all the registered workflows.
+   *
+   * @return the default network group
+   */
+  public static NetworkGroup getDefaultNetworkGroup()
+  {
+    return defaultNetworkGroup;
+  }
+
+
+  /**
+   * Rebuilds the list of naming contexts handled by the network group.
+   * This operation should be performed whenever a workflow topology
+   * has been updated (workflow registration or de-registration).
+   */
+  private void rebuildNamingContextList()
+  {
+    // reset lists of naming contexts
+    namingContexts.resetLists();
+
+    // a registered workflow with no parent is a naming context
+    for (WorkflowTopologyNode curWorkflow: registeredWorkflows)
+    {
+      WorkflowTopologyNode parent = curWorkflow.getParent();
+      if (parent == null)
+      {
+        namingContexts.addNamingContext (curWorkflow);
+      }
+    }
+  }
+
+
+  /**
+   * Checks whether a base DN has been already registered with
+   * the network group.
+   *
+   * @param baseDN  the base DN to check
+   * @return <code>false</code> if the base DN is registered with the
+   *         network group, <code>false</code> otherwise
+   */
+  private boolean baseDNAlreadyRegistered (
+      DN baseDN
+      )
+  {
+    // returned result
+    boolean alreadyRegistered = false;
+
+    // go through the list of registered workflow and check whether a base DN
+    // has already been used in a registered workflow
+    for (WorkflowTopologyNode curWorkflow: registeredWorkflows)
+    {
+      DN curDN = curWorkflow.getBaseDN();
+      if (baseDN.equals (curDN))
+      {
+        alreadyRegistered = true;
+        break;
+      }
+    }
+
+    // check done
+    return alreadyRegistered;
+  }
+
+
+  /**
+   * Returns the list of naming contexts handled by the network group.
+   *
+   * @return the list of naming contexts
+   */
+  public NetworkGroupNamingContexts getNamingContexts()
+  {
+    return namingContexts;
+  }
+
+
+  /**
+   * Dumps info from the current network group for debug purpose.
+   *
+   * @param  leftMargin  white spaces used to indent traces
+   * @return a string buffer that contains trace information
+   */
+  public StringBuffer toString (String leftMargin)
+  {
+    StringBuffer sb = new StringBuffer();
+    String newMargin = leftMargin + "   ";
+
+    sb.append (leftMargin + "Networkgroup (" + networkGroupName+ "\n");
+    sb.append (leftMargin + "List of registered workflows:\n");
+    for (WorkflowTopologyNode w: registeredWorkflows)
+    {
+      sb.append (w.toString (newMargin));
+    }
+
+    namingContexts.toString (leftMargin);
+
+    sb.append (leftMargin + "rootDSEWorkflow:\n");
+    if (rootDSEWorkflow == null)
+    {
+      sb.append (newMargin + "null\n");
+    }
+    else
+    {
+      sb.append (rootDSEWorkflow.toString (newMargin));
+    }
+
+    return sb;
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupCriteria.java b/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupCriteria.java
new file mode 100644
index 0000000..8f9b9ba
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupCriteria.java
@@ -0,0 +1,46 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+/**
+ * This class defines the network group criteria. A criterion is used
+ * by the network groups to determine whether a client connection belongs
+ * to the network group or not.
+ */
+public class NetworkGroupCriteria
+{
+
+  /**
+   * Creates a new instance of the network group criteria.
+   */
+  public NetworkGroupCriteria()
+  {
+    // No implementation is required.
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupNamingContexts.java b/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupNamingContexts.java
new file mode 100644
index 0000000..f71df58
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupNamingContexts.java
@@ -0,0 +1,157 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import java.util.ArrayList;
+
+
+/**
+ * This classes defines a list of naming contexts for a network group.
+ */
+public class NetworkGroupNamingContexts
+{
+  // List of naming contexts.
+  private ArrayList<WorkflowTopologyNode> namingContexts = null;
+
+  // List of public naming contexts.
+  private ArrayList<WorkflowTopologyNode> publicNamingContexts = null;
+
+  // List of private naming contexts.
+  private ArrayList<WorkflowTopologyNode> privateNamingContexts = null;
+
+  /**
+   * Create a list of naming contexts for a network group.
+   */
+  public NetworkGroupNamingContexts()
+  {
+    // create the lists of naming contexts
+    resetLists();
+  }
+
+
+  /**
+   * Reset the list of naming contexts.
+   */
+  public void resetLists()
+  {
+    namingContexts        = new ArrayList<WorkflowTopologyNode>();
+    privateNamingContexts = new ArrayList<WorkflowTopologyNode>();
+    publicNamingContexts  = new ArrayList<WorkflowTopologyNode>();
+  }
+
+
+  /**
+   * Add a workflow in the list of naming context.
+   *
+   * @param workflow  the workflow to add in the list of naming contexts
+   */
+  public void addNamingContext (
+      WorkflowTopologyNode workflow
+      )
+  {
+    // add the workflow to the list of naming context
+    namingContexts.add (workflow);
+
+    // add the workflow to the private/public list of naming contexts
+    if (workflow.isPrivate())
+    {
+      privateNamingContexts.add (workflow);
+    }
+    else
+    {
+      publicNamingContexts.add (workflow);
+    }
+  }
+
+
+  /**
+   * Get the list of naming contexts.
+   *
+   * @return the list of all the naming contexts
+   */
+  public ArrayList<WorkflowTopologyNode> getNamingContexts()
+  {
+    return namingContexts;
+  }
+
+
+  /**
+   * Get the list of private naming contexts.
+   *
+   * @return the list of private naming contexts
+   */
+  public ArrayList<WorkflowTopologyNode> getPrivateNamingContexts()
+  {
+    return privateNamingContexts;
+  }
+
+
+  /**
+   * Get the list of public naming contexts.
+   *
+   * @return the list of public naming contexts
+   */
+  public ArrayList<WorkflowTopologyNode> getPublicNamingContexts()
+  {
+    return publicNamingContexts;
+  }
+
+
+  /**
+   * Dumps info from the current networkk group for debug purpose.
+   *
+   * @param  leftMargin  white spaces used to indent traces
+   * @return a string buffer that contains trace information
+   */
+  public StringBuffer toString (String leftMargin)
+  {
+    StringBuffer sb = new StringBuffer();
+    String newMargin = leftMargin + "   ";
+
+    sb.append (leftMargin + "List of naming contexts:\n");
+    for (WorkflowTopologyNode w: namingContexts)
+    {
+      sb.append (w.toString (newMargin));
+    }
+
+    sb.append (leftMargin + "List of PRIVATE naming contexts:\n");
+    for (WorkflowTopologyNode w: privateNamingContexts)
+    {
+      sb.append (w.toString (newMargin));
+    }
+
+    sb.append (leftMargin + "List of PUBLIC naming contexts:\n");
+    for (WorkflowTopologyNode w: publicNamingContexts)
+    {
+      sb.append (w.toString (newMargin));
+    }
+
+    return sb;
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupPolicy.java b/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupPolicy.java
new file mode 100644
index 0000000..f29eb29
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/NetworkGroupPolicy.java
@@ -0,0 +1,46 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+/**
+ * This class defines the network group policy. A client connection
+ * that belongs to a network group has to comply with the policies
+ * attach to the network group.
+ */
+public class NetworkGroupPolicy
+{
+
+  /**
+   * Creates a new instance of the network group policy.
+   */
+  public NetworkGroupPolicy()
+  {
+    // No implementation is required.
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java b/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java
index 855f691..0a20345 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java
@@ -27,19 +27,19 @@
 package org.opends.server.core;
 
 
-
 import java.util.ArrayList;
 import java.util.Set;
 
 import org.opends.server.controls.EntryChangeNotificationControl;
 import org.opends.server.controls.PersistentSearchChangeType;
 import org.opends.server.types.Control;
+import org.opends.server.types.DN;
 import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchScope;
+import org.opends.server.workflowelement.localbackend.*;
 
 import static org.opends.server.loggers.debug.DebugLogger.*;
 import org.opends.server.loggers.debug.DebugTracer;
@@ -181,7 +181,7 @@
    * @param  addOperation  The add operation that has been processed.
    * @param  entry         The entry that was added.
    */
-  public void processAdd(AddOperation addOperation, Entry entry)
+  public void processAdd(LocalBackendAddOperation addOperation, Entry entry)
   {
     // See if we care about add operations.
     if (! changeTypes.contains(PersistentSearchChangeType.ADD))
@@ -296,7 +296,8 @@
    * @param  deleteOperation  The delete operation that has been processed.
    * @param  entry            The entry that was removed.
    */
-  public void processDelete(DeleteOperation deleteOperation, Entry entry)
+  public void processDelete(LocalBackendDeleteOperation deleteOperation,
+      Entry entry)
   {
     // See if we care about delete operations.
     if (! changeTypes.contains(PersistentSearchChangeType.DELETE))
@@ -412,7 +413,8 @@
    * @param  oldEntry         The entry before the modification was applied.
    * @param  newEntry         The entry after the modification was applied.
    */
-  public void processModify(ModifyOperation modifyOperation, Entry oldEntry,
+  public void processModify(LocalBackendModifyOperation modifyOperation,
+                            Entry oldEntry,
                             Entry newEntry)
   {
     // See if we care about modify operations.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/PluginConfigManager.java b/opendj-sdk/opends/src/server/org/opends/server/core/PluginConfigManager.java
index ae23fab..a5a75a2 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/PluginConfigManager.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/PluginConfigManager.java
@@ -80,6 +80,28 @@
 import org.opends.server.types.SearchResultReference;
 
 import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.operation.PostOperationAddOperation;
+import org.opends.server.types.operation.PostOperationBindOperation;
+import org.opends.server.types.operation.PostOperationDeleteOperation;
+import org.opends.server.types.operation.PostOperationModifyOperation;
+import org.opends.server.types.operation.PostOperationSearchOperation;
+import org.opends.server.types.operation.PostResponseAddOperation;
+import org.opends.server.types.operation.PostResponseBindOperation;
+import org.opends.server.types.operation.PostResponseDeleteOperation;
+import org.opends.server.types.operation.PostResponseModifyOperation;
+import org.opends.server.types.operation.PostResponseSearchOperation;
+import org.opends.server.types.operation.PreOperationAddOperation;
+import org.opends.server.types.operation.PreOperationBindOperation;
+import org.opends.server.types.operation.PreOperationDeleteOperation;
+import org.opends.server.types.operation.PreOperationModifyOperation;
+import org.opends.server.types.operation.PreOperationSearchOperation;
+import org.opends.server.types.operation.PreParseAddOperation;
+import org.opends.server.types.operation.PreParseBindOperation;
+import org.opends.server.types.operation.PreParseDeleteOperation;
+import org.opends.server.types.operation.PreParseModifyOperation;
+import org.opends.server.types.operation.PreParseSearchOperation;
+import org.opends.server.workflowelement.localbackend.*;
+
 import static org.opends.server.loggers.ErrorLogger.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
 import org.opends.server.loggers.debug.DebugTracer;
@@ -1758,8 +1780,8 @@
    *
    * @return  The result of processing the pre-parse add plugins.
    */
-  public PreParsePluginResult invokePreParseAddPlugins(AddOperation
-                                                            addOperation)
+  public PreParsePluginResult invokePreParseAddPlugins(
+      PreParseAddOperation addOperation)
   {
     PreParsePluginResult result = null;
 
@@ -1839,7 +1861,7 @@
    * @return  The result of processing the pre-parse bind plugins.
    */
   public PreParsePluginResult invokePreParseBindPlugins(
-                                   BindOperation bindOperation)
+                                   PreParseBindOperation bindOperation)
   {
     PreParsePluginResult result = null;
 
@@ -2001,7 +2023,7 @@
    * @return  The result of processing the pre-parse delete plugins.
    */
   public PreParsePluginResult invokePreParseDeletePlugins(
-                                   DeleteOperation deleteOperation)
+                              PreParseDeleteOperation deleteOperation)
   {
     PreParsePluginResult result = null;
 
@@ -2159,13 +2181,13 @@
    * Invokes the set of pre-parse modify plugins that have been configured in
    * the Directory Server.
    *
-   * @param  modifyOperation  The modify operation for which to invoke the
+   * @param  operation  The modify operation for which to invoke the
    *                          pre-parse plugins.
    *
    * @return  The result of processing the pre-parse modify plugins.
    */
   public PreParsePluginResult invokePreParseModifyPlugins(
-                                   ModifyOperation modifyOperation)
+                                   PreParseModifyOperation operation)
   {
     PreParsePluginResult result = null;
 
@@ -2173,7 +2195,7 @@
     {
       try
       {
-        result = p.doPreParse(modifyOperation);
+        result = p.doPreParse(operation);
       }
       catch (Exception e)
       {
@@ -2185,17 +2207,17 @@
         int    msgID   = MSGID_PLUGIN_PRE_PARSE_PLUGIN_EXCEPTION;
         String message =
              getMessage(msgID,
-                        modifyOperation.getOperationType().getOperationName(),
+                        operation.getOperationType().getOperationName(),
                         String.valueOf(p.getPluginEntryDN()),
-                        modifyOperation.getConnectionID(),
-                        modifyOperation.getOperationID(),
+                        operation.getConnectionID(),
+                        operation.getOperationID(),
                         stackTraceToSingleLineString(e));
         logError(ErrorLogCategory.PLUGIN, ErrorLogSeverity.SEVERE_ERROR,
                  message, msgID);
 
-        modifyOperation.setResultCode(
+        operation.setResultCode(
                              DirectoryServer.getServerErrorResultCode());
-        modifyOperation.appendErrorMessage(message);
+        operation.appendErrorMessage(message);
 
         return new PreParsePluginResult(false, false, true);
       }
@@ -2205,16 +2227,16 @@
         int    msgID   = MSGID_PLUGIN_PRE_PARSE_PLUGIN_RETURNED_NULL;
         String message =
              getMessage(msgID,
-                        modifyOperation.getOperationType().getOperationName(),
+                        operation.getOperationType().getOperationName(),
                         String.valueOf(p.getPluginEntryDN()),
-                        modifyOperation.getConnectionID(),
-                        modifyOperation.getOperationID());
+                        operation.getConnectionID(),
+                        operation.getOperationID());
         logError(ErrorLogCategory.PLUGIN, ErrorLogSeverity.SEVERE_ERROR,
                  message, msgID);
 
-        modifyOperation.setResultCode(
+        operation.setResultCode(
                              DirectoryServer.getServerErrorResultCode());
-        modifyOperation.appendErrorMessage(message);
+        operation.appendErrorMessage(message);
 
         return new PreParsePluginResult(false, false, true);
       }
@@ -2329,7 +2351,7 @@
    * @return  The result of processing the pre-parse search plugins.
    */
   public PreParsePluginResult invokePreParseSearchPlugins(
-                                   SearchOperation searchOperation)
+                                   PreParseSearchOperation searchOperation)
   {
     PreParsePluginResult result = null;
 
@@ -2493,7 +2515,7 @@
    * @return  The result of processing the pre-operation add plugins.
    */
   public PreOperationPluginResult invokePreOperationAddPlugins(
-                                       AddOperation addOperation)
+                                       PreOperationAddOperation addOperation)
   {
     PreOperationPluginResult result = null;
 
@@ -2573,7 +2595,7 @@
    * @return  The result of processing the pre-operation bind plugins.
    */
   public PreOperationPluginResult invokePreOperationBindPlugins(
-                                       BindOperation bindOperation)
+                                       PreOperationBindOperation bindOperation)
   {
     PreOperationPluginResult result = null;
 
@@ -2735,7 +2757,7 @@
    * @return  The result of processing the pre-operation delete plugins.
    */
   public PreOperationPluginResult invokePreOperationDeletePlugins(
-                                       DeleteOperation deleteOperation)
+                                  PreOperationDeleteOperation deleteOperation)
   {
     PreOperationPluginResult result = null;
 
@@ -2899,7 +2921,7 @@
    * @return  The result of processing the pre-operation modify plugins.
    */
   public PreOperationPluginResult invokePreOperationModifyPlugins(
-                                       ModifyOperation modifyOperation)
+                                  PreOperationModifyOperation modifyOperation)
   {
     PreOperationPluginResult result = null;
 
@@ -3063,7 +3085,7 @@
    * @return  The result of processing the pre-operation search plugins.
    */
   public PreOperationPluginResult invokePreOperationSearchPlugins(
-                                       SearchOperation searchOperation)
+                                  PreOperationSearchOperation searchOperation)
   {
     PreOperationPluginResult result = null;
 
@@ -3227,7 +3249,7 @@
    * @return  The result of processing the post-operation add plugins.
    */
   public PostOperationPluginResult invokePostOperationAddPlugins(
-                                        AddOperation addOperation)
+                                        PostOperationAddOperation addOperation)
   {
     PostOperationPluginResult result = null;
 
@@ -3307,7 +3329,7 @@
    * @return  The result of processing the post-operation bind plugins.
    */
   public PostOperationPluginResult invokePostOperationBindPlugins(
-                                        BindOperation bindOperation)
+                                   PostOperationBindOperation bindOperation)
   {
     PostOperationPluginResult result = null;
 
@@ -3469,7 +3491,7 @@
    * @return  The result of processing the post-operation delete plugins.
    */
   public PostOperationPluginResult invokePostOperationDeletePlugins(
-                                        DeleteOperation deleteOperation)
+                                   PostOperationDeleteOperation deleteOperation)
   {
     PostOperationPluginResult result = null;
 
@@ -3633,7 +3655,7 @@
    * @return  The result of processing the post-operation modify plugins.
    */
   public PostOperationPluginResult invokePostOperationModifyPlugins(
-                                        ModifyOperation modifyOperation)
+                                   PostOperationModifyOperation modifyOperation)
   {
     PostOperationPluginResult result = null;
 
@@ -3797,7 +3819,7 @@
    * @return  The result of processing the post-operation search plugins.
    */
   public PostOperationPluginResult invokePostOperationSearchPlugins(
-                                        SearchOperation searchOperation)
+                                   PostOperationSearchOperation searchOperation)
   {
     PostOperationPluginResult result = null;
 
@@ -3961,7 +3983,7 @@
    * @return  The result of processing the post-response add plugins.
    */
   public PostResponsePluginResult invokePostResponseAddPlugins(
-                                       AddOperation addOperation)
+                                       PostResponseAddOperation addOperation)
   {
     PostResponsePluginResult result = null;
 
@@ -4035,7 +4057,7 @@
    * @return  The result of processing the post-response bind plugins.
    */
   public PostResponsePluginResult invokePostResponseBindPlugins(
-                                       BindOperation bindOperation)
+                                       PostResponseBindOperation bindOperation)
   {
     PostResponsePluginResult result = null;
 
@@ -4183,7 +4205,7 @@
    * @return  The result of processing the post-response delete plugins.
    */
   public PostResponsePluginResult invokePostResponseDeletePlugins(
-                                       DeleteOperation deleteOperation)
+                          PostResponseDeleteOperation deleteOperation)
   {
     PostResponsePluginResult result = null;
 
@@ -4331,7 +4353,7 @@
    * @return  The result of processing the post-response modify plugins.
    */
   public PostResponsePluginResult invokePostResponseModifyPlugins(
-                                       ModifyOperation modifyOperation)
+                                  PostResponseModifyOperation modifyOperation)
   {
     PostResponsePluginResult result = null;
 
@@ -4479,7 +4501,7 @@
    * @return  The result of processing the post-response search plugins.
    */
   public PostResponsePluginResult invokePostResponseSearchPlugins(
-                                       SearchOperation searchOperation)
+                                  PostResponseSearchOperation searchOperation)
   {
     PostResponsePluginResult result = null;
 
@@ -4541,7 +4563,75 @@
     return result;
   }
 
+  /**
+   * Invokes the set of search result entry plugins that have been configured
+   * in the Directory Server.
+   *
+   * @param  searchOperation  The search operation for which to invoke the
+   *                          search result entry plugins.
+   * @param  searchEntry      The search result entry to be processed.
+   *
+   * @return  The result of processing the search result entry plugins.
+   */
+  public SearchEntryPluginResult invokeSearchResultEntryPlugins(
+                                 LocalBackendSearchOperation searchOperation,
+                                 SearchResultEntry searchEntry)
+  {
+    SearchEntryPluginResult result = null;
 
+    for (DirectoryServerPlugin p : searchResultEntryPlugins)
+    {
+      try
+      {
+        result = p.processSearchEntry(searchOperation, searchEntry);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        int    msgID   = MSGID_PLUGIN_SEARCH_ENTRY_PLUGIN_EXCEPTION;
+        String message = getMessage(msgID, String.valueOf(p.getPluginEntryDN()),
+                                    searchOperation.getConnectionID(),
+                                    searchOperation.getOperationID(),
+                                    String.valueOf(searchEntry.getDN()),
+                                    stackTraceToSingleLineString(e));
+        logError(ErrorLogCategory.PLUGIN, ErrorLogSeverity.SEVERE_ERROR,
+                 message, msgID);
+
+        return new SearchEntryPluginResult(false, false, false, false);
+      }
+
+      if (result == null)
+      {
+        int    msgID   = MSGID_PLUGIN_SEARCH_ENTRY_PLUGIN_RETURNED_NULL;
+        String message = getMessage(msgID, String.valueOf(p.getPluginEntryDN()),
+                                    searchOperation.getConnectionID(),
+                                    searchOperation.getOperationID(),
+                                    String.valueOf(searchEntry.getDN()));
+        logError(ErrorLogCategory.PLUGIN, ErrorLogSeverity.SEVERE_ERROR,
+                 message, msgID);
+
+        return new SearchEntryPluginResult(false, false, false, false);
+      }
+      else if (result.connectionTerminated() ||
+               (! result.continuePluginProcessing()))
+      {
+        return result;
+      }
+    }
+
+    if (result == null)
+    {
+      // This should only happen if there were no search result entry plugins
+      // registered, which is fine.
+      result = SearchEntryPluginResult.SUCCESS;
+    }
+
+    return result;
+  }
 
   /**
    * Invokes the set of search result entry plugins that have been configured
@@ -4554,7 +4644,7 @@
    * @return  The result of processing the search result entry plugins.
    */
   public SearchEntryPluginResult invokeSearchResultEntryPlugins(
-                                      SearchOperation searchOperation,
+                                      SearchOperationBasis searchOperation,
                                       SearchResultEntry searchEntry)
   {
     SearchEntryPluginResult result = null;
@@ -4613,7 +4703,75 @@
     return result;
   }
 
+  /**
+   * Invokes the set of search result reference plugins that have been
+   * configured in the Directory Server.
+   *
+   * @param  searchOperation  The search operation for which to invoke the
+   *                          search result reference plugins.
+   * @param  searchReference  The search result reference to be processed.
+   *
+   * @return  The result of processing the search result reference plugins.
+   */
+  public SearchReferencePluginResult invokeSearchResultReferencePlugins(
+                                   LocalBackendSearchOperation searchOperation,
+                                   SearchResultReference searchReference)
+  {
+    SearchReferencePluginResult result = null;
 
+    for (DirectoryServerPlugin p : searchResultReferencePlugins)
+    {
+      try
+      {
+        result = p.processSearchReference(searchOperation, searchReference);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        int    msgID   = MSGID_PLUGIN_SEARCH_REFERENCE_PLUGIN_EXCEPTION;
+        String message = getMessage(msgID, String.valueOf(p.getPluginEntryDN()),
+                                    searchOperation.getConnectionID(),
+                                    searchOperation.getOperationID(),
+                                    searchReference.getReferralURLString(),
+                                    stackTraceToSingleLineString(e));
+        logError(ErrorLogCategory.PLUGIN, ErrorLogSeverity.SEVERE_ERROR,
+                 message, msgID);
+
+        return new SearchReferencePluginResult(false, false, false, false);
+      }
+
+      if (result == null)
+      {
+        int    msgID   = MSGID_PLUGIN_SEARCH_REFERENCE_PLUGIN_RETURNED_NULL;
+        String message = getMessage(msgID, String.valueOf(p.getPluginEntryDN()),
+                                    searchOperation.getConnectionID(),
+                                    searchOperation.getOperationID(),
+                                    searchReference.getReferralURLString());
+        logError(ErrorLogCategory.PLUGIN, ErrorLogSeverity.SEVERE_ERROR,
+                 message, msgID);
+
+        return new SearchReferencePluginResult(false, false, false, false);
+      }
+      else if (result.connectionTerminated() ||
+               (! result.continuePluginProcessing()))
+      {
+        return result;
+      }
+    }
+
+    if (result == null)
+    {
+      // This should only happen if there were no search result reference
+      // plugins registered, which is fine.
+      result = SearchReferencePluginResult.SUCCESS;
+    }
+
+    return result;
+  }
 
   /**
    * Invokes the set of search result reference plugins that have been
@@ -4626,7 +4784,7 @@
    * @return  The result of processing the search result reference plugins.
    */
   public SearchReferencePluginResult invokeSearchResultReferencePlugins(
-                                          SearchOperation searchOperation,
+                                          SearchOperationBasis searchOperation,
                                           SearchResultReference searchReference)
   {
     SearchReferencePluginResult result = null;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/RootDseWorkflowTopology.java b/opendj-sdk/opends/src/server/org/opends/server/core/RootDseWorkflowTopology.java
new file mode 100644
index 0000000..1e526d2
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/RootDseWorkflowTopology.java
@@ -0,0 +1,175 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import org.opends.server.types.DN;
+import org.opends.server.types.Operation;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.SearchScope;
+
+
+/**
+ * This class implements the workflow node that handles the root DSE entry.
+ * As opposed to the WorkflowTopologyNode class, the root DSE node has no
+ * parent node nor subordinate nodes. Instead, the root DSE node has a set
+ * of naming contexts, each of which is a WorkflowTopologyNode object with
+ * no parent.
+ */
+public class RootDseWorkflowTopology extends WorkflowTopology
+{
+
+  // The naming contexts known by the root DSE. These naming contexts
+  // are defined in the scope of a network group.
+  private NetworkGroupNamingContexts namingContexts = null;
+
+
+  /**
+   * Creates a workflow node to handle the root DSE entry.
+   *
+   * @param workflowImpl    the workflow which contains the processing for
+   *                        the root DSE backend
+   * @param namingContexts  the list of naming contexts being registered
+   *                        with the network group the root DSE belongs to
+   */
+  public RootDseWorkflowTopology(
+      WorkflowImpl               workflowImpl,
+      NetworkGroupNamingContexts namingContexts
+      )
+  {
+    super(workflowImpl);
+    this.namingContexts = namingContexts;
+  }
+
+
+  /**
+   * Executes an operation on the root DSE entry.
+   *
+   * @param operation the operation to execute
+   */
+  public void execute(
+      Operation operation
+      )
+  {
+    // Execute the operation.
+    OperationType operationType = operation.getOperationType();
+    if (operationType != OperationType.SEARCH)
+    {
+      // Execute the operation
+      getWorkflowImpl().execute(operation);
+    }
+    else
+    {
+      // Execute the SEARCH operation
+      executeSearch((SearchOperation) operation);
+    }
+  }
+
+
+  /**
+   * Executes a search operation on the the root DSE entry.
+   *
+   * @param searchOp the operation to execute
+   */
+  private void executeSearch(
+      SearchOperation searchOp
+      )
+  {
+    // Keep a the original search scope because we will alter it in the
+    // operation.
+    SearchScope originalScope = searchOp.getScope();
+
+    // Search base?
+    // The root DSE entry itself is never returned unless the operation
+    // is a search base on the null suffix.
+    if (originalScope == SearchScope.BASE_OBJECT)
+    {
+      getWorkflowImpl().execute(searchOp);
+      return;
+    }
+
+    // Create a workflow result code in case we need to perform search in
+    // subordinate workflows.
+    WorkflowResultCode workflowResultCode = new WorkflowResultCode(
+        searchOp.getResultCode(), searchOp.getErrorMessage());
+
+    // The search scope is not 'base', so let's do a search on all the public
+    // naming contexts with appropriate new search scope and new base DN.
+    SearchScope newScope = elaborateScopeForSearchInSubordinates(originalScope);
+    searchOp.setScope(newScope);
+    DN originalBaseDN = searchOp.getBaseDN();
+
+    for (WorkflowTopologyNode namingContext:
+         namingContexts.getPublicNamingContexts())
+    {
+      // We have to change the operation request base DN to match the
+      // subordinate workflow base DN. Otherwise the workflow will
+      // return a no such entry result code as the operation request
+      // base DN is a superior of the workflow base DN!
+      DN ncDN = namingContext.getBaseDN();
+
+      // Set the new request base DN then do execute the operation
+      // in the naming context workflow.
+      searchOp.setBaseDN(ncDN);
+      namingContext.execute(searchOp);
+      boolean sendReferenceEntry =
+        workflowResultCode.elaborateGlobalResultCode(
+          searchOp.getResultCode(), searchOp.getErrorMessage());
+      if (sendReferenceEntry)
+      {
+        // TODO jdemendi - turn a referral result code into a reference entry
+        // and send the reference entry to the client application
+      }
+    }
+
+    // Now restore the original request base DN and original search scope
+    searchOp.setBaseDN(originalBaseDN);
+    searchOp.setScope(originalScope);
+
+    // Set the operation result code and error message
+    searchOp.setResultCode(workflowResultCode.resultCode());
+    searchOp.setErrorMessage(workflowResultCode.errorMessage());
+  }
+
+
+  /**
+   * Dumps info from the current workflow for debug purpose.
+   *
+   * @param leftMargin  white spaces used to indent the traces
+   * @return a string buffer that contains trace information
+   */
+  public StringBuffer toString(String leftMargin)
+  {
+    StringBuffer sb = new StringBuffer();
+
+    // display the baseDN
+    sb.append(leftMargin + "Workflow baseDN:[ \"\" ]\n");
+
+    return sb;
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java
index 007fd1d..80fb4fa 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java
@@ -26,365 +26,29 @@
  */
 package org.opends.server.core;
 
-
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.opends.server.api.Backend;
-import org.opends.server.api.ClientConnection;
-import org.opends.server.api.plugin.PostOperationPluginResult;
-import org.opends.server.api.plugin.PreOperationPluginResult;
-import org.opends.server.api.plugin.PreParsePluginResult;
-import org.opends.server.api.plugin.SearchEntryPluginResult;
-import org.opends.server.api.plugin.SearchReferencePluginResult;
-import org.opends.server.controls.*;
-import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.protocols.ldap.LDAPFilter;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.AttributeValue;
+import org.opends.server.controls.MatchedValuesControl;
 import org.opends.server.types.ByteString;
-import org.opends.server.types.CancelledOperationException;
-import org.opends.server.types.CancelRequest;
-import org.opends.server.types.CancelResult;
 import org.opends.server.types.Control;
-import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DN;
 import org.opends.server.types.DereferencePolicy;
 import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DisconnectReason;
-import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
-import org.opends.server.types.FilterType;
-import org.opends.server.types.LDAPException;
 import org.opends.server.types.Operation;
-import org.opends.server.types.OperationType;
-import org.opends.server.types.Privilege;
 import org.opends.server.types.RawFilter;
-import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchResultReference;
 import org.opends.server.types.SearchScope;
-import org.opends.server.types.operation.PostOperationSearchOperation;
-import org.opends.server.types.operation.PostResponseSearchOperation;
-import org.opends.server.types.operation.PreOperationSearchOperation;
-import org.opends.server.types.operation.PreParseSearchOperation;
-import org.opends.server.types.operation.SearchEntrySearchOperation;
-import org.opends.server.types.operation.SearchReferenceSearchOperation;
-import org.opends.server.util.TimeThread;
-
-import static org.opends.server.core.CoreConstants.*;
-import static org.opends.server.loggers.AccessLogger.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
-import static org.opends.server.messages.CoreMessages.*;
-import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
-
 
 /**
- * This class defines an operation that may be used to locate entries in the
- * Directory Server based on a given set of criteria.
+ * This interface defines an operation used to search for entries
+ * in the Directory Server.
  */
-public class SearchOperation
-       extends Operation
-       implements PreParseSearchOperation, PreOperationSearchOperation,
-                  PostOperationSearchOperation, PostResponseSearchOperation,
-                  SearchEntrySearchOperation, SearchReferenceSearchOperation
+public interface SearchOperation extends Operation
 {
-  /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = getTracer();
-
-  // Indicates whether a search result done response has been sent to the
-  // client.
-  private AtomicBoolean responseSent;
-
-  // Indicates whether the client is able to handle referrals.
-  private boolean clientAcceptsReferrals;
-
-  // Indicates whether to include the account usable control with search result
-  // entries.
-  private boolean includeUsableControl;
-
-  // Indicates whether to only real attributes should be returned.
-  private boolean realAttributesOnly;
-
-  // Indicates whether LDAP subentries should be returned.
-  private boolean returnLDAPSubentries;
-
-  // Indicates whether to include attribute types only or both types and values.
-  private boolean typesOnly;
-
-  // Indicates whether to only virtual attributes should be returned.
-  private boolean virtualAttributesOnly;
-
-  // The raw, unprocessed base DN as included in the request from the client.
-  private ByteString rawBaseDN;
-
-  // The cancel request that has been issued for this search operation.
-  private CancelRequest cancelRequest;
-
-  // The dereferencing policy for the search operation.
-  private DereferencePolicy derefPolicy;
-
-  // The base DN for the search operation.
-  private DN baseDN;
-
-  // The proxied authorization target DN for this operation.
-  private DN proxiedAuthorizationDN;
-
-  // The number of entries that have been sent to the client.
-  private int entriesSent;
-
-  // The number of search result references that have been sent to the client.
-  private int referencesSent;
-
-  // The size limit for the search operation.
-  private int sizeLimit;
-
-  // The time limit for the search operation.
-  private int timeLimit;
-
-  // The set of attributes that should be returned in matching entries.
-  private LinkedHashSet<String> attributes;
-
-  // The set of response controls for this search operation.
-  private List<Control> responseControls;
-
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
-  // The time that the search time limit has expired.
-  private long timeLimitExpiration;
-
-  // The matched values control associated with this search operation.
-  private MatchedValuesControl matchedValuesControl;
-
-  // The persistent search associated with this search operation.
-  private PersistentSearch persistentSearch;
-
-  // The raw, unprocessed filter as included in the request from the client.
-  private RawFilter rawFilter;
-
-  // The search filter for the search operation.
-  private SearchFilter filter;
-
-  // The search scope for the search operation.
-  private SearchScope scope;
-
-
-
-  /**
-   * Creates a new search operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  rawBaseDN         The raw, unprocessed base DN as included in the
-   *                           request from the client.
-   * @param  scope             The scope for this search operation.
-   * @param  derefPolicy       The alias dereferencing policy for this search
-   *                           operation.
-   * @param  sizeLimit         The size limit for this search operation.
-   * @param  timeLimit         The time limit for this search operation.
-   * @param  typesOnly         The typesOnly flag for this search operation.
-   * @param  rawFilter         the raw, unprocessed filter as included in the
-   *                           request from the client.
-   * @param  attributes        The requested attributes for this search
-   *                           operation.
-   */
-  public SearchOperation(ClientConnection clientConnection, long operationID,
-                         int messageID, List<Control> requestControls,
-                         ByteString rawBaseDN, SearchScope scope,
-                         DereferencePolicy derefPolicy, int sizeLimit,
-                         int timeLimit, boolean typesOnly, RawFilter rawFilter,
-                         LinkedHashSet<String> attributes)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.rawBaseDN   = rawBaseDN;
-    this.scope       = scope;
-    this.derefPolicy = derefPolicy;
-    this.sizeLimit   = sizeLimit;
-    this.timeLimit   = timeLimit;
-    this.typesOnly   = typesOnly;
-    this.rawFilter   = rawFilter;
-
-    if (attributes == null)
-    {
-      this.attributes  = new LinkedHashSet<String>(0);
-    }
-    else
-    {
-      this.attributes  = attributes;
-    }
-
-
-    if (clientConnection.getSizeLimit() <= 0)
-    {
-      this.sizeLimit = sizeLimit;
-    }
-    else
-    {
-      if (sizeLimit <= 0)
-      {
-        this.sizeLimit = clientConnection.getSizeLimit();
-      }
-      else
-      {
-        this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit());
-      }
-    }
-
-
-    if (clientConnection.getTimeLimit() <= 0)
-    {
-      this.timeLimit = timeLimit;
-    }
-    else
-    {
-      if (timeLimit <= 0)
-      {
-        this.timeLimit = clientConnection.getTimeLimit();
-      }
-      else
-      {
-        this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit());
-      }
-    }
-
-
-    baseDN                 = null;
-    filter                 = null;
-    entriesSent            = 0;
-    referencesSent         = 0;
-    responseControls       = new ArrayList<Control>();
-    cancelRequest          = null;
-    clientAcceptsReferrals = true;
-    includeUsableControl   = false;
-    responseSent           = new AtomicBoolean(false);
-    persistentSearch       = null;
-    returnLDAPSubentries   = false;
-    matchedValuesControl   = null;
-    realAttributesOnly     = false;
-    virtualAttributesOnly  = false;
-  }
-
-
-
-  /**
-   * Creates a new search operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this operation
-   *                           is associated.
-   * @param  operationID       The operation ID for this operation.
-   * @param  messageID         The message ID of the request with which this
-   *                           operation is associated.
-   * @param  requestControls   The set of controls included in the request.
-   * @param  baseDN            The base DN for this search operation.
-   * @param  scope             The scope for this search operation.
-   * @param  derefPolicy       The alias dereferencing policy for this search
-   *                           operation.
-   * @param  sizeLimit         The size limit for this search operation.
-   * @param  timeLimit         The time limit for this search operation.
-   * @param  typesOnly         The typesOnly flag for this search operation.
-   * @param  filter            The filter for this search operation.
-   * @param  attributes        The attributes for this search operation.
-   */
-  public SearchOperation(ClientConnection clientConnection, long operationID,
-                         int messageID, List<Control> requestControls,
-                         DN baseDN, SearchScope scope,
-                         DereferencePolicy derefPolicy, int sizeLimit,
-                         int timeLimit, boolean typesOnly, SearchFilter filter,
-                         LinkedHashSet<String> attributes)
-  {
-    super(clientConnection, operationID, messageID, requestControls);
-
-
-    this.baseDN      = baseDN;
-    this.scope       = scope;
-    this.derefPolicy = derefPolicy;
-    this.sizeLimit   = sizeLimit;
-    this.timeLimit   = timeLimit;
-    this.typesOnly   = typesOnly;
-    this.filter      = filter;
-
-    if (attributes == null)
-    {
-      this.attributes = new LinkedHashSet<String>(0);
-    }
-    else
-    {
-      this.attributes  = attributes;
-    }
-
-    rawBaseDN = new ASN1OctetString(baseDN.toString());
-    rawFilter = new LDAPFilter(filter);
-
-
-    if (clientConnection.getSizeLimit() <= 0)
-    {
-      this.sizeLimit = sizeLimit;
-    }
-    else
-    {
-      if (sizeLimit <= 0)
-      {
-        this.sizeLimit = clientConnection.getSizeLimit();
-      }
-      else
-      {
-        this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit());
-      }
-    }
-
-
-    if (clientConnection.getTimeLimit() <= 0)
-    {
-      this.timeLimit = timeLimit;
-    }
-    else
-    {
-      if (timeLimit <= 0)
-      {
-        this.timeLimit = clientConnection.getTimeLimit();
-      }
-      else
-      {
-        this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit());
-      }
-    }
-
-
-    entriesSent            = 0;
-    referencesSent         = 0;
-    responseControls       = new ArrayList<Control>();
-    cancelRequest          = null;
-    clientAcceptsReferrals = true;
-    includeUsableControl   = false;
-    responseSent           = new AtomicBoolean(false);
-    persistentSearch       = null;
-    returnLDAPSubentries   = false;
-    matchedValuesControl   = null;
-  }
-
-
 
   /**
    * Retrieves the raw, unprocessed base DN as included in the request from the
@@ -394,12 +58,7 @@
    * @return  The raw, unprocessed base DN as included in the request from the
    *          client.
    */
-  public final ByteString getRawBaseDN()
-  {
-    return rawBaseDN;
-  }
-
-
+  public abstract ByteString getRawBaseDN();
 
   /**
    * Specifies the raw, unprocessed base DN as included in the request from the
@@ -408,14 +67,7 @@
    * @param  rawBaseDN  The raw, unprocessed base DN as included in the request
    *                    from the client.
    */
-  public final void setRawBaseDN(ByteString rawBaseDN)
-  {
-    this.rawBaseDN = rawBaseDN;
-
-    baseDN = null;
-  }
-
-
+  public abstract void setRawBaseDN(ByteString rawBaseDN);
 
   /**
    * Retrieves the base DN for this search operation.  This should not be called
@@ -425,12 +77,7 @@
    * @return  The base DN for this search operation, or <CODE>null</CODE> if the
    *          raw base DN has not yet been processed.
    */
-  public final DN getBaseDN()
-  {
-    return baseDN;
-  }
-
-
+  public abstract DN getBaseDN();
 
   /**
    * Specifies the base DN for this search operation.  This method is only
@@ -438,24 +85,14 @@
    *
    * @param  baseDN  The base DN for this search operation.
    */
-  public final void setBaseDN(DN baseDN)
-  {
-    this.baseDN = baseDN;
-  }
-
-
+  public abstract void setBaseDN(DN baseDN);
 
   /**
    * Retrieves the scope for this search operation.
    *
    * @return  The scope for this search operation.
    */
-  public final SearchScope getScope()
-  {
-    return scope;
-  }
-
-
+  public abstract SearchScope getScope();
 
   /**
    * Specifies the scope for this search operation.  This should only be called
@@ -463,24 +100,14 @@
    *
    * @param  scope  The scope for this search operation.
    */
-  public final void setScope(SearchScope scope)
-  {
-    this.scope = scope;
-  }
-
-
+  public abstract void setScope(SearchScope scope);
 
   /**
    * Retrieves the alias dereferencing policy for this search operation.
    *
    * @return  The alias dereferencing policy for this search operation.
    */
-  public final DereferencePolicy getDerefPolicy()
-  {
-    return derefPolicy;
-  }
-
-
+  public abstract DereferencePolicy getDerefPolicy();
 
   /**
    * Specifies the alias dereferencing policy for this search operation.  This
@@ -489,24 +116,14 @@
    * @param  derefPolicy  The alias dereferencing policy for this search
    *                      operation.
    */
-  public final void setDerefPolicy(DereferencePolicy derefPolicy)
-  {
-    this.derefPolicy = derefPolicy;
-  }
-
-
+  public abstract void setDerefPolicy(DereferencePolicy derefPolicy);
 
   /**
    * Retrieves the size limit for this search operation.
    *
    * @return  The size limit for this search operation.
    */
-  public final int getSizeLimit()
-  {
-    return sizeLimit;
-  }
-
-
+  public abstract int getSizeLimit();
 
   /**
    * Specifies the size limit for this search operation.  This should only be
@@ -514,24 +131,21 @@
    *
    * @param  sizeLimit  The size limit for this search operation.
    */
-  public final void setSizeLimit(int sizeLimit)
-  {
-    this.sizeLimit = sizeLimit;
-  }
-
-
+  public abstract void setSizeLimit(int sizeLimit);
 
   /**
    * Retrieves the time limit for this search operation.
    *
    * @return  The time limit for this search operation.
    */
-  public final int getTimeLimit()
-  {
-    return timeLimit;
-  }
+  public abstract int getTimeLimit();
 
-
+  /**
+   * Get the time after which the search time limit has expired.
+   *
+   * @return the timeLimitExpiration
+   */
+  public abstract Long getTimeLimitExpiration();
 
   /**
    * Specifies the time limit for this search operation.  This should only be
@@ -539,24 +153,14 @@
    *
    * @param  timeLimit  The time limit for this search operation.
    */
-  public final void setTimeLimit(int timeLimit)
-  {
-    this.timeLimit = timeLimit;
-  }
-
-
+  public abstract void setTimeLimit(int timeLimit);
 
   /**
    * Retrieves the typesOnly flag for this search operation.
    *
    * @return  The typesOnly flag for this search operation.
    */
-  public final boolean getTypesOnly()
-  {
-    return typesOnly;
-  }
-
-
+  public abstract boolean getTypesOnly();
 
   /**
    * Specifies the typesOnly flag for this search operation.  This should only
@@ -564,12 +168,7 @@
    *
    * @param  typesOnly  The typesOnly flag for this search operation.
    */
-  public final void setTypesOnly(boolean typesOnly)
-  {
-    this.typesOnly = typesOnly;
-  }
-
-
+  public abstract void setTypesOnly(boolean typesOnly);
 
   /**
    * Retrieves the raw, unprocessed search filter as included in the request
@@ -580,12 +179,7 @@
    * @return  The raw, unprocessed search filter as included in the request from
    *          the client.
    */
-  public final RawFilter getRawFilter()
-  {
-    return rawFilter;
-  }
-
-
+  public abstract RawFilter getRawFilter();
 
   /**
    * Specifies the raw, unprocessed search filter as included in the request
@@ -594,14 +188,7 @@
    * @param  rawFilter  The raw, unprocessed search filter as included in the
    *                    request from the client.
    */
-  public final void setRawFilter(RawFilter rawFilter)
-  {
-    this.rawFilter = rawFilter;
-
-    filter = null;
-  }
-
-
+  public abstract void setRawFilter(RawFilter rawFilter);
 
   /**
    * Retrieves the filter for this search operation.  This should not be called
@@ -611,12 +198,7 @@
    * @return  The filter for this search operation, or <CODE>null</CODE> if the
    *          raw filter has not yet been processed.
    */
-  public final SearchFilter getFilter()
-  {
-    return filter;
-  }
-
-
+  public abstract SearchFilter getFilter();
 
   /**
    * Retrieves the set of requested attributes for this search operation.  Its
@@ -624,12 +206,7 @@
    *
    * @return  The set of requested attributes for this search operation.
    */
-  public final LinkedHashSet<String> getAttributes()
-  {
-    return attributes;
-  }
-
-
+  public abstract LinkedHashSet<String> getAttributes();
 
   /**
    * Specifies the set of requested attributes for this search operation.  It
@@ -638,19 +215,7 @@
    * @param  attributes  The set of requested attributes for this search
    *                     operation.
    */
-  public final void setAttributes(LinkedHashSet<String> attributes)
-  {
-    if (attributes == null)
-    {
-      this.attributes.clear();
-    }
-    else
-    {
-      this.attributes = attributes;
-    }
-  }
-
-
+  public abstract void setAttributes(LinkedHashSet<String> attributes);
 
   /**
    * Retrieves the number of entries sent to the client for this search
@@ -659,12 +224,7 @@
    * @return  The number of entries sent to the client for this search
    *          operation.
    */
-  public final int getEntriesSent()
-  {
-    return entriesSent;
-  }
-
-
+  public abstract int getEntriesSent();
 
   /**
    * Retrieves the number of search references sent to the client for this
@@ -673,45 +233,7 @@
    * @return  The number of search references sent to the client for this search
    *          operation.
    */
-  public final int getReferencesSent()
-  {
-    return referencesSent;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
+  public abstract int getReferencesSent();
 
   /**
    * Used as a callback for backends to indicate that the provided entry matches
@@ -728,439 +250,7 @@
    *          <CODE>false</CODE> if not for some reason (e.g., the size limit
    *          has been reached or the search has been abandoned).
    */
-  public final boolean returnEntry(Entry entry, List<Control> controls)
-  {
-    // See if the operation has been abandoned.  If so, then don't send the
-    // entry and indicate that the search should end.
-    if (cancelRequest != null)
-    {
-      setResultCode(ResultCode.CANCELED);
-      return false;
-    }
-
-    // See if the size limit has been exceeded.  If so, then don't send the
-    // entry and indicate that the search should end.
-    if ((sizeLimit > 0) && (entriesSent >= sizeLimit))
-    {
-      setResultCode(ResultCode.SIZE_LIMIT_EXCEEDED);
-      appendErrorMessage(getMessage(MSGID_SEARCH_SIZE_LIMIT_EXCEEDED,
-                                    sizeLimit));
-      return false;
-    }
-
-    // See if the time limit has expired.  If so, then don't send the entry and
-    // indicate that the search should end.
-    if ((timeLimit > 0) && (TimeThread.getTime() >= timeLimitExpiration))
-    {
-      setResultCode(ResultCode.TIME_LIMIT_EXCEEDED);
-      appendErrorMessage(getMessage(MSGID_SEARCH_TIME_LIMIT_EXCEEDED,
-                                    timeLimit));
-      return false;
-    }
-
-    // Determine whether the provided entry is a subentry and if so whether it
-    // should be returned.
-    if ((scope != SearchScope.BASE_OBJECT) && (! returnLDAPSubentries) &&
-        entry.isLDAPSubentry())
-    {
-      // Check to see if the filter contains an equality element with the
-      // objectclass attribute type and a value of "ldapSubentry".  If so, then
-      // we'll return it anyway.  Technically, this isn't part of the
-      // specification so we don't need to get carried away with really in-depth
-      // checks.
-      switch (filter.getFilterType())
-      {
-        case AND:
-        case OR:
-          for (SearchFilter f : filter.getFilterComponents())
-          {
-            if ((f.getFilterType() == FilterType.EQUALITY) &&
-                (f.getAttributeType().isObjectClassType()))
-            {
-              AttributeValue v = f.getAssertionValue();
-              if (toLowerCase(v.getStringValue()).equals("ldapsubentry"))
-              {
-                returnLDAPSubentries = true;
-              }
-              break;
-            }
-          }
-          break;
-        case EQUALITY:
-          AttributeType t = filter.getAttributeType();
-          if (t.isObjectClassType())
-          {
-            AttributeValue v = filter.getAssertionValue();
-            if (toLowerCase(v.getStringValue()).equals("ldapsubentry"))
-            {
-              returnLDAPSubentries = true;
-            }
-          }
-          break;
-      }
-
-      if (! returnLDAPSubentries)
-      {
-        // We still shouldn't return it even based on the filter.  Just throw it
-        // away without doing anything.
-        return true;
-      }
-    }
-
-
-    // Determine whether to include the account usable control.  If so, then
-    // create it now.
-    if (includeUsableControl)
-    {
-      try
-      {
-        // FIXME -- Need a way to enable PWP debugging.
-        PasswordPolicyState pwpState = new PasswordPolicyState(entry, false,
-                                                               false);
-
-        boolean isInactive           = pwpState.isDisabled() ||
-                                       pwpState.isAccountExpired();
-        boolean isLocked             = pwpState.lockedDueToFailures() ||
-                                       pwpState.lockedDueToMaximumResetAge() ||
-                                       pwpState.lockedDueToIdleInterval();
-        boolean isReset              = pwpState.mustChangePassword();
-        boolean isExpired            = pwpState.isPasswordExpired();
-
-        if (isInactive || isLocked || isReset || isExpired)
-        {
-          int secondsBeforeUnlock  = pwpState.getSecondsUntilUnlock();
-          int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
-
-          if (controls == null)
-          {
-            controls = new ArrayList<Control>(1);
-          }
-
-          controls.add(new AccountUsableResponseControl(isInactive, isReset,
-                                isExpired, remainingGraceLogins, isLocked,
-                                secondsBeforeUnlock));
-        }
-        else
-        {
-          if (controls == null)
-          {
-            controls = new ArrayList<Control>(1);
-          }
-
-          int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration();
-          controls.add(new AccountUsableResponseControl(
-                                secondsBeforeExpiration));
-        }
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-      }
-    }
-
-    // Check to see if the entry can be read by the client.
-    SearchResultEntry tmpSearchEntry = new SearchResultEntry(entry,
-        controls);
-    if (AccessControlConfigManager.getInstance()
-        .getAccessControlHandler().maySend(this, tmpSearchEntry) == false) {
-      return true;
-    }
-
-    // Make a copy of the entry and pare it down to only include the set
-    // of
-    // requested attributes.
-    Entry entryToReturn;
-    if ((attributes == null) || attributes.isEmpty())
-    {
-      entryToReturn = entry.duplicateWithoutOperationalAttributes(typesOnly,
-                                                                  true);
-    }
-    else
-    {
-      entryToReturn = entry.duplicateWithoutAttributes();
-
-      for (String attrName : attributes)
-      {
-        if (attrName.equals("*"))
-        {
-          // This is a special placeholder indicating that all user attributes
-          // should be returned.
-          if (typesOnly)
-          {
-            // First, add the placeholder for the objectclass attribute.
-            AttributeType ocType =
-                 DirectoryServer.getObjectClassAttributeType();
-            List<Attribute> ocList = new ArrayList<Attribute>(1);
-            ocList.add(new Attribute(ocType));
-            entryToReturn.putAttribute(ocType, ocList);
-          }
-          else
-          {
-            // First, add the objectclass attribute.
-            Attribute ocAttr = entry.getObjectClassAttribute();
-            try
-            {
-              entryToReturn.setObjectClasses(ocAttr.getValues());
-            }
-            catch (DirectoryException e)
-            {
-              // We cannot get this exception because the object classes have
-              // already been validated in the entry they came from.
-            }
-          }
-
-          // Next iterate through all the user attributes and include them.
-          for (AttributeType t : entry.getUserAttributes().keySet())
-          {
-            List<Attribute> attrList =
-                 entry.duplicateUserAttribute(t, null, typesOnly);
-            entryToReturn.putAttribute(t, attrList);
-          }
-
-          continue;
-        }
-        else if (attrName.equals("+"))
-        {
-          // This is a special placeholder indicating that all operational
-          // attributes should be returned.
-          for (AttributeType t : entry.getOperationalAttributes().keySet())
-          {
-            List<Attribute> attrList =
-                 entry.duplicateOperationalAttribute(t, null, typesOnly);
-            entryToReturn.putAttribute(t, attrList);
-          }
-
-          continue;
-        }
-
-        String lowerName;
-        HashSet<String> options;
-        int semicolonPos = attrName.indexOf(';');
-        if (semicolonPos > 0)
-        {
-          lowerName = toLowerCase(attrName.substring(0, semicolonPos));
-          int nextPos = attrName.indexOf(';', semicolonPos+1);
-          options = new HashSet<String>();
-          while (nextPos > 0)
-          {
-            options.add(attrName.substring(semicolonPos+1, nextPos));
-
-            semicolonPos = nextPos;
-            nextPos = attrName.indexOf(';', semicolonPos+1);
-          }
-
-          options.add(attrName.substring(semicolonPos+1));
-        }
-        else
-        {
-          lowerName = toLowerCase(attrName);
-          options = null;
-        }
-
-
-        AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
-        if (attrType == null)
-        {
-          boolean added = false;
-          for (AttributeType t : entry.getUserAttributes().keySet())
-          {
-            if (t.hasNameOrOID(lowerName))
-            {
-              List<Attribute> attrList =
-                   entry.duplicateUserAttribute(t, options, typesOnly);
-              if (attrList != null)
-              {
-                entryToReturn.putAttribute(t, attrList);
-
-                added = true;
-                break;
-              }
-            }
-          }
-
-          if (added)
-          {
-            continue;
-          }
-
-          for (AttributeType t : entry.getOperationalAttributes().keySet())
-          {
-            if (t.hasNameOrOID(lowerName))
-            {
-              List<Attribute> attrList =
-                   entry.duplicateOperationalAttribute(t, options, typesOnly);
-              if (attrList != null)
-              {
-                entryToReturn.putAttribute(t, attrList);
-
-                break;
-              }
-            }
-          }
-        }
-        else
-        {
-          if (attrType.isObjectClassType()) {
-            if (typesOnly)
-            {
-              AttributeType ocType =
-                   DirectoryServer.getObjectClassAttributeType();
-              List<Attribute> ocList = new ArrayList<Attribute>(1);
-              ocList.add(new Attribute(ocType));
-              entryToReturn.putAttribute(ocType, ocList);
-            }
-            else
-            {
-              List<Attribute> attrList = new ArrayList<Attribute>(1);
-              attrList.add(entry.getObjectClassAttribute());
-              entryToReturn.putAttribute(attrType, attrList);
-            }
-          }
-          else
-          {
-            List<Attribute> attrList =
-                 entry.duplicateOperationalAttribute(attrType, options,
-                                                     typesOnly);
-            if (attrList == null)
-            {
-              attrList = entry.duplicateUserAttribute(attrType, options,
-                                                      typesOnly);
-            }
-            if (attrList != null)
-            {
-              entryToReturn.putAttribute(attrType, attrList);
-            }
-          }
-        }
-      }
-    }
-
-
-    if (realAttributesOnly)
-    {
-      entryToReturn.stripVirtualAttributes();
-    }
-    else if (virtualAttributesOnly)
-    {
-      entryToReturn.stripRealAttributes();
-    }
-
-
-    // If there is a matched values control, then further pare down the entry
-    // based on the filters that it contains.
-    if ((matchedValuesControl != null) && (! typesOnly))
-    {
-      // First, look at the set of objectclasses.
-      AttributeType attrType = DirectoryServer.getObjectClassAttributeType();
-      Iterator<String> ocIterator =
-           entryToReturn.getObjectClasses().values().iterator();
-      while (ocIterator.hasNext())
-      {
-        String ocName = ocIterator.next();
-        AttributeValue v = new AttributeValue(attrType,
-                                              new ASN1OctetString(ocName));
-        if (! matchedValuesControl.valueMatches(attrType, v))
-        {
-          ocIterator.remove();
-        }
-      }
-
-
-      // Next, the set of user attributes.
-      for (AttributeType t : entryToReturn.getUserAttributes().keySet())
-      {
-        for (Attribute a : entryToReturn.getUserAttribute(t))
-        {
-          Iterator<AttributeValue> valueIterator = a.getValues().iterator();
-          while (valueIterator.hasNext())
-          {
-            AttributeValue v = valueIterator.next();
-            if (! matchedValuesControl.valueMatches(t, v))
-            {
-              valueIterator.remove();
-            }
-          }
-        }
-      }
-
-
-      // Then the set of operational attributes.
-      for (AttributeType t : entryToReturn.getOperationalAttributes().keySet())
-      {
-        for (Attribute a : entryToReturn.getOperationalAttribute(t))
-        {
-          Iterator<AttributeValue> valueIterator = a.getValues().iterator();
-          while (valueIterator.hasNext())
-          {
-            AttributeValue v = valueIterator.next();
-            if (! matchedValuesControl.valueMatches(t, v))
-            {
-              valueIterator.remove();
-            }
-          }
-        }
-      }
-    }
-
-
-    // Convert the provided entry to a search result entry.
-    SearchResultEntry searchEntry = new SearchResultEntry(entryToReturn,
-                                                          controls);
-
-    // Strip out any attributes that the client does not have access to.
-
-    // FIXME: need some way to prevent plugins from adding attributes or
-    // values that the client is not permitted to see.
-    searchEntry = AccessControlConfigManager.getInstance()
-        .getAccessControlHandler().filterEntry(this, searchEntry);
-
-    // Invoke any search entry plugins that may be registered with the server.
-    SearchEntryPluginResult pluginResult =
-         DirectoryServer.getPluginConfigManager().
-              invokeSearchResultEntryPlugins(this, searchEntry);
-    if (pluginResult.connectionTerminated())
-    {
-      // We won't attempt to send this entry, and we won't continue with
-      // any processing.  Just update the operation to indicate that it was
-      // cancelled and return false.
-      setResultCode(ResultCode.CANCELED);
-      appendErrorMessage(getMessage(MSGID_CANCELED_BY_SEARCH_ENTRY_DISCONNECT,
-                                    String.valueOf(entry.getDN())));
-      return false;
-    }
-
-
-    // Send the entry to the client.
-    if (pluginResult.sendEntry())
-    {
-      try
-      {
-        clientConnection.sendSearchEntry(this, searchEntry);
-
-        // Log the entry sent to the client.
-        logSearchResultEntry(this, searchEntry);
-
-        entriesSent++;
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResponseData(de);
-        return false;
-      }
-    }
-
-    return pluginResult.continueSearch();
-  }
-
-
+  public abstract boolean returnEntry(Entry entry, List<Control> controls);
 
   /**
    * Used as a callback for backends to indicate that the provided search
@@ -1174,100 +264,7 @@
    *          <CODE>false</CODE> if not for some reason (e.g., the size limit
    *          has been reached or the search has been abandoned).
    */
-  public final boolean returnReference(SearchResultReference reference)
-  {
-    // See if the operation has been abandoned.  If so, then don't send the
-    // reference and indicate that the search should end.
-    if (cancelRequest != null)
-    {
-      setResultCode(ResultCode.CANCELED);
-      return false;
-    }
-
-
-    // See if the time limit has expired.  If so, then don't send the entry and
-    // indicate that the search should end.
-    if ((timeLimit > 0) && (TimeThread.getTime() >= timeLimitExpiration))
-    {
-      setResultCode(ResultCode.TIME_LIMIT_EXCEEDED);
-      appendErrorMessage(getMessage(MSGID_SEARCH_TIME_LIMIT_EXCEEDED,
-                                    timeLimit));
-      return false;
-    }
-
-
-    // See if we know that this client can't handle referrals.  If so, then
-    // don't even try to send it.
-    if (! clientAcceptsReferrals)
-    {
-      return true;
-    }
-
-
-    // See if the client has permission to read this reference.
-    if (AccessControlConfigManager.getInstance()
-        .getAccessControlHandler().maySend(this, reference) == false) {
-      return true;
-    }
-
-
-    // Invoke any search reference plugins that may be registered with the
-    // server.
-    SearchReferencePluginResult pluginResult =
-         DirectoryServer.getPluginConfigManager().
-              invokeSearchResultReferencePlugins(this, reference);
-    if (pluginResult.connectionTerminated())
-    {
-      // We won't attempt to send this entry, and we won't continue with
-      // any processing.  Just update the operation to indicate that it was
-      // cancelled and return false.
-      setResultCode(ResultCode.CANCELED);
-      appendErrorMessage(getMessage(MSGID_CANCELED_BY_SEARCH_REF_DISCONNECT,
-           String.valueOf(reference.getReferralURLString())));
-      return false;
-    }
-
-
-    // Send the reference to the client.  Note that this could throw an
-    // exception, which would indicate that the associated client can't handle
-    // referrals.  If that't the case, then set a flag so we'll know not to try
-    // to send any more.
-    if (pluginResult.sendReference())
-    {
-      try
-      {
-        if (clientConnection.sendSearchReference(this, reference))
-        {
-          // Log the entry sent to the client.
-          logSearchResultReference(this, reference);
-          referencesSent++;
-
-          // FIXME -- Should the size limit apply here?
-        }
-        else
-        {
-          // We know that the client can't handle referrals, so we won't try to
-          // send it any more.
-          clientAcceptsReferrals = false;
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResponseData(de);
-        return false;
-      }
-    }
-
-
-    return pluginResult.continueSearch();
-  }
-
-
+  public abstract boolean returnReference(SearchResultReference reference);
 
   /**
    * Sends the search result done message to the client.  Note that this method
@@ -1277,174 +274,182 @@
    * message should have been set for this operation before this method is
    * called.
    */
-  public final void sendSearchResultDone()
-  {
-    // Send the search result done message to the client.  We want to make sure
-    // that this only gets sent once, and it's possible that this could be
-    // multithreaded in the event of a persistent search, so do it safely.
-    if (responseSent.compareAndSet(false, true))
-    {
-      // Send the response to the client.
-      clientConnection.sendResponse(this);
-
-      // Log the search result.
-      logSearchResultDone(this);
-
-
-      // Invoke the post-response search plugins.
-      DirectoryServer.getPluginConfigManager().
-           invokePostResponseSearchPlugins(this);
-    }
-  }
-
-
+  public abstract void sendSearchResultDone();
 
   /**
-   * {@inheritDoc}
+   * Set the time after which the search time limit has expired.
+   *
+   * @param timeLimitExpiration - Time after which the search has expired
    */
-  @Override()
-  public final OperationType getOperationType()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    return OperationType.SEARCH;
-  }
-
-
+  public abstract void setTimeLimitExpiration(Long timeLimitExpiration);
 
   /**
-   * {@inheritDoc}
+   * Indicates whether LDAP subentries should be returned or not.
+   *
+   * @return true if the LDAP subentries should be returned, false otherwise
    */
-  @Override()
-  public final void disconnectClient(DisconnectReason disconnectReason,
-                                     boolean sendNotification, String message,
-                                     int messageID)
-  {
-    // Before calling clientConnection.disconnect, we need to mark this
-    // operation as cancelled so that the attempt to cancel it later won't cause
-    // an unnecessary delay.
-    setCancelResult(CancelResult.CANCELED);
-
-    clientConnection.disconnect(disconnectReason, sendNotification, message,
-                                messageID);
-  }
-
-
+  public abstract boolean isReturnLDAPSubentries();
 
   /**
-   * {@inheritDoc}
+   * Set the flag indicating wether the LDAP subentries should be returned.
+   *
+   * @param returnLDAPSubentries - Boolean indicating wether the LDAP
+   *                               subentries should be returned or not
    */
-  @Override()
-  public final String[][] getRequestLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
-
-    String attrs;
-    if ((attributes == null) || attributes.isEmpty())
-    {
-      attrs = null;
-    }
-    else
-    {
-      StringBuilder attrBuffer = new StringBuilder();
-      Iterator<String> iterator = attributes.iterator();
-      attrBuffer.append(iterator.next());
-
-      while (iterator.hasNext())
-      {
-        attrBuffer.append(", ");
-        attrBuffer.append(iterator.next());
-      }
-
-      attrs = attrBuffer.toString();
-    }
-
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_BASE_DN, String.valueOf(rawBaseDN) },
-      new String[] { LOG_ELEMENT_SCOPE, String.valueOf(scope) },
-      new String[] { LOG_ELEMENT_SIZE_LIMIT, String.valueOf(sizeLimit) },
-      new String[] { LOG_ELEMENT_TIME_LIMIT, String.valueOf(timeLimit) },
-      new String[] { LOG_ELEMENT_FILTER, String.valueOf(rawFilter) },
-      new String[] { LOG_ELEMENT_REQUESTED_ATTRIBUTES, attrs }
-    };
-  }
-
-
+  public abstract void setReturnLDAPSubentries(boolean returnLDAPSubentries);
 
   /**
-   * {@inheritDoc}
+   * The matched values control associated with this search operation.
+   *
+   * @return the match values control
    */
-  @Override()
-  public final String[][] getResponseLogElements()
-  {
-    // Note that no debugging will be done in this method because it is a likely
-    // candidate for being called by the logging subsystem.
+  public abstract MatchedValuesControl getMatchedValuesControl();
 
-    String resultCode = String.valueOf(getResultCode().getIntValue());
+  /**
+   * Set the match values control.
+   *
+   * @param controls - The matched values control
+   */
+  public abstract void setMatchedValuesControl(MatchedValuesControl controls);
 
-    String errorMessage;
-    StringBuilder errorMessageBuffer = getErrorMessage();
-    if (errorMessageBuffer == null)
-    {
-      errorMessage = null;
-    }
-    else
-    {
-      errorMessage = errorMessageBuffer.toString();
-    }
+  /**
+   * Indicates whether to include the account usable response control with
+   * search result entries or not.
+   *
+   * @return true if the usable control has to be part of the search result
+   *         entry
+   */
+  public abstract boolean isIncludeUsableControl();
 
-    String matchedDNStr;
-    DN matchedDN = getMatchedDN();
-    if (matchedDN == null)
-    {
-      matchedDNStr = null;
-    }
-    else
-    {
-      matchedDNStr = matchedDN.toString();
-    }
+  /**
+   * Specify whether to include the account usable response control within the
+   * search result entries.
+   *
+   * @param includeUsableControl - True if the account usable response control
+   *                               has to be included within the search result
+   *                               entries, false otherwise
+   */
+  public abstract void setIncludeUsableControl(boolean includeUsableControl);
 
-    String referrals;
-    List<String> referralURLs = getReferralURLs();
-    if ((referralURLs == null) || referralURLs.isEmpty())
-    {
-      referrals = null;
-    }
-    else
-    {
-      StringBuilder buffer = new StringBuilder();
-      Iterator<String> iterator = referralURLs.iterator();
-      buffer.append(iterator.next());
+  /**
+   * Register the psearch in the search operation.
+   *
+   * @param psearch - Persistent search associated to that operation
+   */
+  public abstract void setPersistentSearch(PersistentSearch psearch);
 
-      while (iterator.hasNext())
-      {
-        buffer.append(", ");
-        buffer.append(iterator.next());
-      }
+  /**
+   * Get the psearch from the search operation.
+   *
+   * @return the psearch, or null if no psearch was registered
+   */
+  public abstract PersistentSearch getPersistentSearch();
 
-      referrals = buffer.toString();
-    }
+  /**
+   * Indicates whether the client is able to handle referrals.
+   *
+   * @return true, if the client is able to handle referrals
+   */
+  public abstract boolean isClientAcceptsReferrals();
 
-    String processingTime =
-         String.valueOf(processingStopTime - processingStartTime);
+  /**
+   * Specify whether the client is able to handle referrals.
+   *
+   * @param clientAcceptReferrals - Boolean set to true if the client
+   *                                can handle referrals
+   */
+  public abstract void setClientAcceptsReferrals(boolean clientAcceptReferrals);
 
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
-      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
-      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
-      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
-      new String[] { LOG_ELEMENT_ENTRIES_SENT, String.valueOf(entriesSent) },
-      new String[] { LOG_ELEMENT_REFERENCES_SENT,
-                     String.valueOf(referencesSent ) },
-      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
-    };
-  }
+  /**
+   * Increments by 1 the number of entries sent to the client for this search
+   * operation.
+   */
+  public abstract void incrementEntriesSent();
 
+  /**
+   * Increments by 1 the number of search references sent to the client for this
+   * search operation.
+   */
+  public abstract void incrementReferencesSent();
 
+  /**
+   * Indicates wether the search result done message has to be sent
+   * to the client, or not.
+   *
+   * @return true if the search result done message is to be sent to the client
+   */
+  public abstract boolean isSendResponse();
+
+  /**
+   * Specify wether the search result done message has to be sent
+   * to the client, or not.
+   *
+   * @param sendResponse - boolean indicating wether the search result done
+   *                       message is to send to the client
+   */
+  public abstract void setSendResponse(boolean sendResponse);
+
+  /**
+   * Returns true if only real attributes should be returned.
+   *
+   * @return true if only real attributes should be returned, false otherwise
+   */
+  public abstract boolean isRealAttributesOnly();
+
+  /**
+   * Specify wether to only return real attributes.
+   *
+   * @param realAttributesOnly - boolean setup to true, if only the real
+   *                             attributes should be returned
+   */
+  public abstract void setRealAttributesOnly(boolean realAttributesOnly);
+
+  /**
+   * Returns true if only virtual attributes should be returned.
+   *
+   * @return true if only virtual attributes should be returned, false
+   *         otherwise
+   */
+  public abstract boolean isVirtualAttributesOnly();
+
+  /**
+   * Specify wether to only return virtual attributes.
+   *
+   * @param virtualAttributesOnly - boolean setup to true, if only the virtual
+   *                                attributes should be returned
+   */
+  public abstract void setVirtualAttributesOnly(boolean virtualAttributesOnly);
+
+  /**
+   * Sends the provided search result entry to the client.
+   *
+   * @param  entry      The search result entry to be sent to
+   *                          the client.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to send the entry to the client and
+   *                              the search should be terminated.
+   */
+  public abstract void sendSearchEntry(SearchResultEntry entry)
+    throws DirectoryException;
+
+  /**
+   * Sends the provided search result reference to the client.
+   *
+   * @param  reference  The search result reference to be sent
+   *                          to the client.
+   *
+   * @return  <CODE>true</CODE> if the client is able to accept
+   *          referrals, or <CODE>false</CODE> if the client cannot
+   *          handle referrals and no more attempts should be made to
+   *          send them for the associated search operation.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to send the reference to the client
+   *                              and the search should be terminated.
+   */
+  public abstract boolean sendSearchReference(SearchResultReference reference)
+    throws DirectoryException;
 
   /**
    * Retrieves the proxied authorization DN for this operation if proxied
@@ -1454,938 +459,17 @@
    *          authorization has been requested, or {@code null} if proxied
    *          authorization has not been requested.
    */
-  public DN getProxiedAuthorizationDN()
-  {
-    return proxiedAuthorizationDN;
-  }
-
-
+  public abstract DN getProxiedAuthorizationDN();
 
   /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final List<Control> getResponseControls()
-  {
-    return responseControls;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void addResponseControl(Control control)
-  {
-    responseControls.add(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void removeResponseControl(Control control)
-  {
-    responseControls.remove(control);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void run()
-  {
-    setResultCode(ResultCode.UNDEFINED);
-    boolean sendResponse = true;
-
-
-    // Get the plugin config manager that will be used for invoking plugins.
-    PluginConfigManager pluginConfigManager =
-         DirectoryServer.getPluginConfigManager();
-    boolean skipPostOperation = false;
-
-
-    // Start the processing timer.
-    processingStartTime = System.currentTimeMillis();
-    if (timeLimit <= 0)
-    {
-      timeLimitExpiration = Long.MAX_VALUE;
-    }
-    else
-    {
-      // FIXME -- Factor in the user's effective time limit.
-      timeLimitExpiration = processingStartTime + (1000L * timeLimit);
-    }
-
-
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
-      return;
-    }
-
-
-    // Create a labeled block of code that we can break out of if a problem is
-    // detected.
-searchProcessing:
-    {
-      PreParsePluginResult preParseResult =
-           pluginConfigManager.invokePreParseSearchPlugins(this);
-      if (preParseResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the request and
-        // result and return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-
-        logSearchRequest(this);
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-      else if (preParseResult.sendResponseImmediately())
-      {
-        skipPostOperation = true;
-        logSearchRequest(this);
-        break searchProcessing;
-      }
-      else if (preParseResult.skipCoreProcessing())
-      {
-        skipPostOperation = false;
-        break searchProcessing;
-      }
-
-
-      // Log the search request message.
-      logSearchRequest(this);
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-
-
-      // Process the search base and filter to convert them from their raw forms
-      // as provided by the client to the forms required for the rest of the
-      // search processing.
-      try
-      {
-        if (baseDN == null)
-        {
-          baseDN = DN.decode(rawBaseDN);
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        setMatchedDN(de.getMatchedDN());
-        setReferralURLs(de.getReferralURLs());
-
-        break searchProcessing;
-      }
-
-      try
-      {
-        if (filter == null)
-        {
-          filter = rawFilter.toSearchFilter();
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        setMatchedDN(de.getMatchedDN());
-        setReferralURLs(de.getReferralURLs());
-
-        break searchProcessing;
-      }
-
-      // Check to see if there are any controls in the request.  If so, then
-      // see if there is any special processing required.
-      boolean       processSearch    = true;
-      List<Control> requestControls  = getRequestControls();
-      if ((requestControls != null) && (! requestControls.isEmpty()))
-      {
-        for (int i=0; i < requestControls.size(); i++)
-        {
-          Control c   = requestControls.get(i);
-          String  oid = c.getOID();
-
-          if (oid.equals(OID_LDAP_ASSERTION))
-          {
-            LDAPAssertionRequestControl assertControl;
-            if (c instanceof LDAPAssertionRequestControl)
-            {
-              assertControl = (LDAPAssertionRequestControl) c;
-            }
-            else
-            {
-              try
-              {
-                assertControl = LDAPAssertionRequestControl.decodeControl(c);
-                requestControls.set(i, assertControl);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-            try
-            {
-              // FIXME -- We need to determine whether the current user has
-              //          permission to make this determination.
-              SearchFilter assertionFilter = assertControl.getSearchFilter();
-              Entry entry;
-              try
-              {
-                entry = DirectoryServer.getEntry(baseDN);
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                setResultCode(de.getResultCode());
-
-                int msgID = MSGID_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION;
-                appendErrorMessage(getMessage(msgID, de.getErrorMessage()));
-
-                break searchProcessing;
-              }
-
-              if (entry == null)
-              {
-                setResultCode(ResultCode.NO_SUCH_OBJECT);
-
-                int msgID = MSGID_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION;
-                appendErrorMessage(getMessage(msgID));
-
-                break searchProcessing;
-              }
-
-
-              if (! assertionFilter.matchesEntry(entry))
-              {
-                setResultCode(ResultCode.ASSERTION_FAILED);
-
-                appendErrorMessage(getMessage(MSGID_SEARCH_ASSERTION_FAILED));
-
-                break searchProcessing;
-              }
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              setResultCode(ResultCode.PROTOCOL_ERROR);
-
-              int msgID = MSGID_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER;
-              appendErrorMessage(getMessage(msgID, de.getErrorMessage()));
-
-              break searchProcessing;
-            }
-          }
-          else if (oid.equals(OID_PROXIED_AUTH_V1))
-          {
-            // The requester must have the PROXIED_AUTH privilige in order to be
-            // able to use this control.
-            if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-            {
-              int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-              appendErrorMessage(getMessage(msgID));
-              setResultCode(ResultCode.AUTHORIZATION_DENIED);
-              break searchProcessing;
-            }
-
-
-            ProxiedAuthV1Control proxyControl;
-            if (c instanceof ProxiedAuthV1Control)
-            {
-              proxyControl = (ProxiedAuthV1Control) c;
-            }
-            else
-            {
-              try
-              {
-                proxyControl = ProxiedAuthV1Control.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-
-            Entry authorizationEntry;
-            try
-            {
-              authorizationEntry = proxyControl.getAuthorizationEntry();
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              setResultCode(de.getResultCode());
-              appendErrorMessage(de.getErrorMessage());
-
-              break searchProcessing;
-            }
-
-            if (AccessControlConfigManager.getInstance().
-                 getAccessControlHandler().isProxiedAuthAllowed(this,
-                                                 authorizationEntry) == false) {
-              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-              int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-              appendErrorMessage(getMessage(msgID, String.valueOf(baseDN)));
-
-              skipPostOperation = true;
-              break searchProcessing;
-            }
-            setAuthorizationEntry(authorizationEntry);
-            if (authorizationEntry == null)
-            {
-              proxiedAuthorizationDN = DN.nullDN();
-            }
-            else
-            {
-              proxiedAuthorizationDN = authorizationEntry.getDN();
-            }
-          }
-          else if (oid.equals(OID_PROXIED_AUTH_V2))
-          {
-            // The requester must have the PROXIED_AUTH privilige in order to be
-            // able to use this control.
-            if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
-            {
-              int msgID = MSGID_PROXYAUTH_INSUFFICIENT_PRIVILEGES;
-              appendErrorMessage(getMessage(msgID));
-              setResultCode(ResultCode.AUTHORIZATION_DENIED);
-              break searchProcessing;
-            }
-
-
-            ProxiedAuthV2Control proxyControl;
-            if (c instanceof ProxiedAuthV2Control)
-            {
-              proxyControl = (ProxiedAuthV2Control) c;
-            }
-            else
-            {
-              try
-              {
-                proxyControl = ProxiedAuthV2Control.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-
-            Entry authorizationEntry;
-            try
-            {
-              authorizationEntry = proxyControl.getAuthorizationEntry();
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              setResultCode(de.getResultCode());
-              appendErrorMessage(de.getErrorMessage());
-
-              break searchProcessing;
-            }
-
-            if (AccessControlConfigManager.getInstance()
-                .getAccessControlHandler().isProxiedAuthAllowed(this,
-                                                 authorizationEntry) == false) {
-              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-              int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-              appendErrorMessage(getMessage(msgID, String.valueOf(baseDN)));
-
-              skipPostOperation = true;
-              break searchProcessing;
-            }
-
-            setAuthorizationEntry(authorizationEntry);
-            if (authorizationEntry == null)
-            {
-              proxiedAuthorizationDN = DN.nullDN();
-            }
-            else
-            {
-              proxiedAuthorizationDN = authorizationEntry.getDN();
-            }
-          }
-          else if (oid.equals(OID_PERSISTENT_SEARCH))
-          {
-            PersistentSearchControl psearchControl;
-            if (c instanceof PersistentSearchControl)
-            {
-              psearchControl = (PersistentSearchControl) c;
-            }
-            else
-            {
-              try
-              {
-                psearchControl = PersistentSearchControl.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-            persistentSearch =
-                 new PersistentSearch(this, psearchControl.getChangeTypes(),
-                                      psearchControl.getReturnECs());
-
-            // If we're only interested in changes, then we don't actually want
-            // to process the search now.
-            if (psearchControl.getChangesOnly())
-            {
-              processSearch = false;
-            }
-          }
-          else if (oid.equals(OID_LDAP_SUBENTRIES))
-          {
-            returnLDAPSubentries = true;
-          }
-          else if (oid.equals(OID_MATCHED_VALUES))
-          {
-            if (c instanceof MatchedValuesControl)
-            {
-              matchedValuesControl = (MatchedValuesControl) c;
-            }
-            else
-            {
-              try
-              {
-                matchedValuesControl = MatchedValuesControl.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-          }
-          else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
-          {
-            includeUsableControl = true;
-          }
-          else if (oid.equals(OID_REAL_ATTRS_ONLY))
-          {
-            realAttributesOnly = true;
-          }
-          else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
-          {
-            virtualAttributesOnly = true;
-          } else if(oid.equals(OID_GET_EFFECTIVE_RIGHTS)) {
-            GetEffectiveRights effectiveRightsControl;
-            if (c instanceof GetEffectiveRights)
-            {
-              effectiveRightsControl = (GetEffectiveRights) c;
-            }
-            else
-            {
-              try
-              {
-                effectiveRightsControl = GetEffectiveRights.decodeControl(c);
-              }
-              catch (LDAPException le)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, le);
-                }
-
-                setResultCode(ResultCode.valueOf(le.getResultCode()));
-                appendErrorMessage(le.getMessage());
-
-                break searchProcessing;
-              }
-            }
-
-              if (!AccessControlConfigManager.getInstance()
-                   .getAccessControlHandler().
-                    isGetEffectiveRightsAllowed(this, effectiveRightsControl)) {
-                 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-                 int msgID =
-                        MSGID_SEARCH_EFFECTIVERIGHTS_INSUFFICIENT_ACCESS_RIGHTS;
-                 appendErrorMessage(getMessage(msgID, String.valueOf(baseDN)));
-
-                 skipPostOperation = true;
-                 break searchProcessing;
-               }
-          }
-
-          // NYI -- Add support for additional controls.
-          else if (c.isCritical())
-          {
-            Backend backend = DirectoryServer.getBackend(baseDN);
-            if ((backend == null) || (! backend.supportsControl(oid)))
-            {
-              setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
-
-              int msgID = MSGID_SEARCH_UNSUPPORTED_CRITICAL_CONTROL;
-              appendErrorMessage(getMessage(msgID, oid));
-
-              break searchProcessing;
-            }
-          }
-        }
-      }
-
-
-      // Check to see if the client has permission to perform the
-      // search.
-
-      // FIXME: for now assume that this will check all permission
-      // pertinent to the operation. This includes proxy authorization
-      // and any other controls specified.
-      if (AccessControlConfigManager.getInstance()
-          .getAccessControlHandler().isAllowed(this) == false) {
-        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
-
-        int msgID = MSGID_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
-        appendErrorMessage(getMessage(msgID, String.valueOf(baseDN)));
-
-        skipPostOperation = true;
-        break searchProcessing;
-      }
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-
-
-      // Invoke the pre-operation search plugins.
-      PreOperationPluginResult preOpResult =
-           pluginConfigManager.invokePreOperationSearchPlugins(this);
-      if (preOpResult.connectionTerminated())
-      {
-        // There's no point in continuing with anything.  Log the request and
-        // result and return.
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-      else if (preOpResult.sendResponseImmediately())
-      {
-        skipPostOperation = true;
-        break searchProcessing;
-      }
-      else if (preOpResult.skipCoreProcessing())
-      {
-        skipPostOperation = false;
-        break searchProcessing;
-      }
-
-
-      // Check for and handle a request to cancel this operation.
-      if (cancelRequest != null)
-      {
-        indicateCancelled(cancelRequest);
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-
-
-      // Get the backend that should hold the search base.  If there is none,
-      // then fail.
-      Backend backend = DirectoryServer.getBackend(baseDN);
-      if (backend == null)
-      {
-        setResultCode(ResultCode.NO_SUCH_OBJECT);
-        appendErrorMessage(getMessage(MSGID_SEARCH_BASE_DOESNT_EXIST,
-                                      String.valueOf(baseDN)));
-        break searchProcessing;
-      }
-
-
-      // We'll set the result code to "success".  If a problem occurs, then it
-      // will be overwritten.
-      setResultCode(ResultCode.SUCCESS);
-
-
-      // If there's a persistent search, then register it with the server.
-      if (persistentSearch != null)
-      {
-        DirectoryServer.registerPersistentSearch(persistentSearch);
-        sendResponse = false;
-      }
-
-
-      // Process the search in the backend and all its subordinates.
-      try
-      {
-        if (processSearch)
-        {
-          searchBackend(backend);
-        }
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        setResultCode(de.getResultCode());
-        appendErrorMessage(de.getErrorMessage());
-        setMatchedDN(de.getMatchedDN());
-        setReferralURLs(de.getReferralURLs());
-
-        if (persistentSearch != null)
-        {
-          DirectoryServer.deregisterPersistentSearch(persistentSearch);
-          sendResponse = true;
-        }
-
-        break searchProcessing;
-      }
-      catch (CancelledOperationException coe)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, coe);
-        }
-
-        CancelResult cancelResult = coe.getCancelResult();
-
-        setCancelResult(cancelResult);
-        setResultCode(cancelResult.getResultCode());
-
-        String message = coe.getMessage();
-        if ((message != null) && (message.length() > 0))
-        {
-          appendErrorMessage(message);
-        }
-
-        if (persistentSearch != null)
-        {
-          DirectoryServer.deregisterPersistentSearch(persistentSearch);
-          sendResponse = true;
-        }
-
-        skipPostOperation = true;
-        break searchProcessing;
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        setResultCode(DirectoryServer.getServerErrorResultCode());
-
-        int msgID = MSGID_SEARCH_BACKEND_EXCEPTION;
-        appendErrorMessage(getMessage(msgID, getExceptionMessage(e)));
-
-        if (persistentSearch != null)
-        {
-          DirectoryServer.deregisterPersistentSearch(persistentSearch);
-          sendResponse = true;
-        }
-
-        skipPostOperation = true;
-        break searchProcessing;
-      }
-    }
-
-
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      indicateCancelled(cancelRequest);
-      processingStopTime = System.currentTimeMillis();
-      logSearchResultDone(this);
-      pluginConfigManager.invokePostResponseSearchPlugins(this);
-      return;
-    }
-
-
-    // Invoke the post-operation search plugins.
-    if (! skipPostOperation)
-    {
-      PostOperationPluginResult postOperationResult =
-           pluginConfigManager.invokePostOperationSearchPlugins(this);
-      if (postOperationResult.connectionTerminated())
-      {
-        setResultCode(ResultCode.CANCELED);
-
-        int msgID = MSGID_CANCELED_BY_POSTOP_DISCONNECT;
-        appendErrorMessage(getMessage(msgID));
-
-        processingStopTime = System.currentTimeMillis();
-        logSearchResultDone(this);
-        pluginConfigManager.invokePostResponseSearchPlugins(this);
-        return;
-      }
-    }
-
-
-    // Indicate that it is now too late to attempt to cancel the operation.
-    setCancelResult(CancelResult.TOO_LATE);
-
-
-    // Stop the processing timer.
-    processingStopTime = System.currentTimeMillis();
-
-
-    // If everything is successful to this point and it is not a persistent
-    // search, then send the search result done message to the client.
-    // Otherwise, we'll want to make the size and time limit values unlimited
-    // to ensure that the remainder of the persistent search isn't subject to
-    // those restrictions.
-    if (sendResponse)
-    {
-      sendSearchResultDone();
-    }
-    else
-    {
-      sizeLimit = 0;
-      timeLimit = 0;
-    }
-  }
-
-
-
-  /**
-   * Processes the search in the provided backend and recursively through its
-   * subordinate backends.
+   * Set the proxied authorization DN for this operation if proxied
+   * authorization has been requested.
    *
-   * @param  backend  The backend in which to process the search.
-   *
-   * @throws  DirectoryException  If a problem occurs while processing the
-   *                              search.
-   *
-   * @throws  CancelledOperationException  If the backend noticed and reacted
-   *                                       to a request to cancel or abandon the
-   *                                       search operation.
+   * @param proxiedAuthorizationDN
+   *          The proxied authorization DN for this operation if proxied
+   *          authorization has been requested, or {@code null} if proxied
+   *          authorization has not been requested.
    */
-  private final void searchBackend(Backend backend)
-          throws DirectoryException, CancelledOperationException
-  {
-    // Check for and handle a request to cancel this operation.
-    if (cancelRequest != null)
-    {
-      setCancelResult(CancelResult.CANCELED);
-      processingStopTime = System.currentTimeMillis();
-      return;
-    }
+  public abstract void setProxiedAuthorizationDN(DN proxiedAuthorizationDN);
 
-
-    // Perform the search in the provided backend.
-    backend.search(this);
-
-
-    // If there are any subordinate backends, then process the search there as
-    // well.
-    Backend[] subBackends = backend.getSubordinateBackends();
-    for (Backend b : subBackends)
-    {
-      DN[] baseDNs = b.getBaseDNs();
-      for (DN dn : baseDNs)
-      {
-        if (dn.isDescendantOf(baseDN))
-        {
-          searchBackend(b);
-          break;
-        }
-      }
-    }
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelResult cancel(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-
-    if (persistentSearch != null)
-    {
-      DirectoryServer.deregisterPersistentSearch(persistentSearch);
-      persistentSearch = null;
-    }
-
-    CancelResult cancelResult = getCancelResult();
-    long stopWaitingTime = System.currentTimeMillis() + 5000;
-    while ((cancelResult == null) &&
-           (System.currentTimeMillis() < stopWaitingTime))
-    {
-      try
-      {
-        Thread.sleep(50);
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-      }
-
-      cancelResult = getCancelResult();
-    }
-
-    if (cancelResult == null)
-    {
-      // This can happen in some rare cases (e.g., if a client disconnects and
-      // there is still a lot of data to send to that client), and in this case
-      // we'll prevent the cancel thread from blocking for a long period of
-      // time.
-      cancelResult = CancelResult.CANNOT_CANCEL;
-    }
-
-    return cancelResult;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final CancelRequest getCancelRequest()
-  {
-    return cancelRequest;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
-  {
-    this.cancelRequest = cancelRequest;
-    return true;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public final void toString(StringBuilder buffer)
-  {
-    buffer.append("SearchOperation(connID=");
-    buffer.append(clientConnection.getConnectionID());
-    buffer.append(", opID=");
-    buffer.append(operationID);
-    buffer.append(", baseDN=");
-    buffer.append(rawBaseDN);
-    buffer.append(", scope=");
-    buffer.append(scope.toString());
-    buffer.append(", filter=");
-    buffer.append(rawFilter.toString());
-    buffer.append(")");
-  }
-}
-
+}
\ No newline at end of file
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java
new file mode 100644
index 0000000..7710d7c
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java
@@ -0,0 +1,1759 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.plugin.PreParsePluginResult;
+import org.opends.server.api.plugin.SearchEntryPluginResult;
+import org.opends.server.api.plugin.SearchReferencePluginResult;
+import org.opends.server.controls.AccountUsableResponseControl;
+import org.opends.server.controls.MatchedValuesControl;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.ldap.LDAPFilter;
+import org.opends.server.types.AbstractOperation;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.FilterType;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.RawFilter;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.SearchResultReference;
+import org.opends.server.types.SearchScope;
+import org.opends.server.types.operation.PostResponseSearchOperation;
+import org.opends.server.types.operation.PreParseSearchOperation;
+import org.opends.server.types.operation.SearchEntrySearchOperation;
+import org.opends.server.types.operation.SearchReferenceSearchOperation;
+import org.opends.server.util.TimeThread;
+
+import static org.opends.server.core.CoreConstants.*;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.AccessLogger.*;
+import static org.opends.server.messages.CoreMessages.*;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.util.StaticUtils.toLowerCase;
+
+/**
+ * This class defines an operation that may be used to locate entries in the
+ * Directory Server based on a given set of criteria.
+ */
+public class SearchOperationBasis
+       extends AbstractOperation
+       implements PreParseSearchOperation,
+                  PostResponseSearchOperation,
+                  SearchEntrySearchOperation,
+                  SearchReferenceSearchOperation,
+                  SearchOperation
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
+
+  // Indicates whether a search result done response has been sent to the
+  // client.
+  private AtomicBoolean responseSent;
+
+  // Indicates whether the client is able to handle referrals.
+  private boolean clientAcceptsReferrals;
+
+  // Indicates whether to include the account usable control with search result
+  // entries.
+  private boolean includeUsableControl;
+
+  // Indicates whether to only real attributes should be returned.
+  private boolean realAttributesOnly;
+
+  // Indicates whether LDAP subentries should be returned.
+  private boolean returnLDAPSubentries;
+
+  // Indicates whether to include attribute types only or both types and values.
+  private boolean typesOnly;
+
+  // Indicates whether to only virtual attributes should be returned.
+  private boolean virtualAttributesOnly;
+
+  // The raw, unprocessed base DN as included in the request from the client.
+  private ByteString rawBaseDN;
+
+  // The cancel request that has been issued for this search operation.
+  private CancelRequest cancelRequest;
+
+  // The dereferencing policy for the search operation.
+  private DereferencePolicy derefPolicy;
+
+  // The base DN for the search operation.
+  private DN baseDN;
+
+  // The proxied authorization target DN for this operation.
+  private DN proxiedAuthorizationDN;
+
+  // The number of entries that have been sent to the client.
+  private int entriesSent;
+
+  // The number of search result references that have been sent to the client.
+  private int referencesSent;
+
+  // The size limit for the search operation.
+  private int sizeLimit;
+
+  // The time limit for the search operation.
+  private int timeLimit;
+
+  // The raw, unprocessed filter as included in the request from the client.
+  private RawFilter rawFilter;
+
+  // The set of attributes that should be returned in matching entries.
+  private LinkedHashSet<String> attributes;
+
+  // The set of response controls for this search operation.
+  private List<Control> responseControls;
+
+  // The time that processing started on this operation.
+  private long processingStartTime;
+
+  // The time that processing ended on this operation.
+  private long processingStopTime;
+
+  // The time that the search time limit has expired.
+  private long timeLimitExpiration;
+
+  // The matched values control associated with this search operation.
+  private MatchedValuesControl matchedValuesControl;
+
+  // The persistent search associated with this search operation.
+  private PersistentSearch persistentSearch;
+
+  // The search filter for the search operation.
+  private SearchFilter filter;
+
+  // The search scope for the search operation.
+  private SearchScope scope;
+
+  // Indicates wether to send the search result done to the client or not
+  private boolean sendResponse = true;
+
+  /**
+   * Creates a new search operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  rawBaseDN         The raw, unprocessed base DN as included in the
+   *                           request from the client.
+   * @param  scope             The scope for this search operation.
+   * @param  derefPolicy       The alias dereferencing policy for this search
+   *                           operation.
+   * @param  sizeLimit         The size limit for this search operation.
+   * @param  timeLimit         The time limit for this search operation.
+   * @param  typesOnly         The typesOnly flag for this search operation.
+   * @param  rawFilter         the raw, unprocessed filter as included in the
+   *                           request from the client.
+   * @param  attributes        The requested attributes for this search
+   *                           operation.
+   */
+  public SearchOperationBasis(ClientConnection clientConnection,
+                         long operationID,
+                         int messageID, List<Control> requestControls,
+                         ByteString rawBaseDN, SearchScope scope,
+                         DereferencePolicy derefPolicy, int sizeLimit,
+                         int timeLimit, boolean typesOnly, RawFilter rawFilter,
+                         LinkedHashSet<String> attributes)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.rawBaseDN   = rawBaseDN;
+    this.scope       = scope;
+    this.derefPolicy = derefPolicy;
+    this.sizeLimit   = sizeLimit;
+    this.timeLimit   = timeLimit;
+    this.typesOnly   = typesOnly;
+    this.rawFilter   = rawFilter;
+
+    if (attributes == null)
+    {
+      this.attributes  = new LinkedHashSet<String>(0);
+    }
+    else
+    {
+      this.attributes  = attributes;
+    }
+
+
+    if (clientConnection.getSizeLimit() <= 0)
+    {
+      this.sizeLimit = sizeLimit;
+    }
+    else
+    {
+      if (sizeLimit <= 0)
+      {
+        this.sizeLimit = clientConnection.getSizeLimit();
+      }
+      else
+      {
+        this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit());
+      }
+    }
+
+
+    if (clientConnection.getTimeLimit() <= 0)
+    {
+      this.timeLimit = timeLimit;
+    }
+    else
+    {
+      if (timeLimit <= 0)
+      {
+        this.timeLimit = clientConnection.getTimeLimit();
+      }
+      else
+      {
+        this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit());
+      }
+    }
+
+
+    baseDN                 = null;
+    filter                 = null;
+    entriesSent            = 0;
+    referencesSent         = 0;
+    responseControls       = new ArrayList<Control>();
+    cancelRequest          = null;
+    clientAcceptsReferrals = true;
+    includeUsableControl   = false;
+    responseSent           = new AtomicBoolean(false);
+    persistentSearch       = null;
+    returnLDAPSubentries   = false;
+    matchedValuesControl   = null;
+    realAttributesOnly     = false;
+    virtualAttributesOnly  = false;
+  }
+
+
+
+  /**
+   * Creates a new search operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this operation
+   *                           is associated.
+   * @param  operationID       The operation ID for this operation.
+   * @param  messageID         The message ID of the request with which this
+   *                           operation is associated.
+   * @param  requestControls   The set of controls included in the request.
+   * @param  baseDN            The base DN for this search operation.
+   * @param  scope             The scope for this search operation.
+   * @param  derefPolicy       The alias dereferencing policy for this search
+   *                           operation.
+   * @param  sizeLimit         The size limit for this search operation.
+   * @param  timeLimit         The time limit for this search operation.
+   * @param  typesOnly         The typesOnly flag for this search operation.
+   * @param  filter            The filter for this search operation.
+   * @param  attributes        The attributes for this search operation.
+   */
+  public SearchOperationBasis(ClientConnection clientConnection,
+                         long operationID,
+                         int messageID, List<Control> requestControls,
+                         DN baseDN, SearchScope scope,
+                         DereferencePolicy derefPolicy, int sizeLimit,
+                         int timeLimit, boolean typesOnly, SearchFilter filter,
+                         LinkedHashSet<String> attributes)
+  {
+    super(clientConnection, operationID, messageID, requestControls);
+
+
+    this.baseDN      = baseDN;
+    this.scope       = scope;
+    this.derefPolicy = derefPolicy;
+    this.sizeLimit   = sizeLimit;
+    this.timeLimit   = timeLimit;
+    this.typesOnly   = typesOnly;
+    this.filter      = filter;
+
+    if (attributes == null)
+    {
+      this.attributes = new LinkedHashSet<String>(0);
+    }
+    else
+    {
+      this.attributes  = attributes;
+    }
+
+    rawBaseDN = new ASN1OctetString(baseDN.toString());
+    rawFilter = new LDAPFilter(filter);
+
+
+    if (clientConnection.getSizeLimit() <= 0)
+    {
+      this.sizeLimit = sizeLimit;
+    }
+    else
+    {
+      if (sizeLimit <= 0)
+      {
+        this.sizeLimit = clientConnection.getSizeLimit();
+      }
+      else
+      {
+        this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit());
+      }
+    }
+
+
+    if (clientConnection.getTimeLimit() <= 0)
+    {
+      this.timeLimit = timeLimit;
+    }
+    else
+    {
+      if (timeLimit <= 0)
+      {
+        this.timeLimit = clientConnection.getTimeLimit();
+      }
+      else
+      {
+        this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit());
+      }
+    }
+
+
+    entriesSent            = 0;
+    referencesSent         = 0;
+    responseControls       = new ArrayList<Control>();
+    cancelRequest          = null;
+    clientAcceptsReferrals = true;
+    includeUsableControl   = false;
+    responseSent           = new AtomicBoolean(false);
+    persistentSearch       = null;
+    returnLDAPSubentries   = false;
+    matchedValuesControl   = null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ByteString getRawBaseDN()
+  {
+    return rawBaseDN;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawBaseDN(ByteString rawBaseDN)
+  {
+    this.rawBaseDN = rawBaseDN;
+
+    baseDN = null;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DN getBaseDN()
+  {
+    try
+    {
+      if (baseDN == null)
+      {
+        baseDN = DN.decode(rawBaseDN);
+      }
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      setResultCode(de.getResultCode());
+      appendErrorMessage(de.getErrorMessage());
+      setMatchedDN(de.getMatchedDN());
+      setReferralURLs(de.getReferralURLs());
+    }
+    return baseDN;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setBaseDN(DN baseDN)
+  {
+    this.baseDN = baseDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final SearchScope getScope()
+  {
+    return scope;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setScope(SearchScope scope)
+  {
+    this.scope = scope;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DereferencePolicy getDerefPolicy()
+  {
+    return derefPolicy;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setDerefPolicy(DereferencePolicy derefPolicy)
+  {
+    this.derefPolicy = derefPolicy;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final int getSizeLimit()
+  {
+    return sizeLimit;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setSizeLimit(int sizeLimit)
+  {
+    this.sizeLimit = sizeLimit;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final int getTimeLimit()
+  {
+    return timeLimit;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setTimeLimit(int timeLimit)
+  {
+    this.timeLimit = timeLimit;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean getTypesOnly()
+  {
+    return typesOnly;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setTypesOnly(boolean typesOnly)
+  {
+    this.typesOnly = typesOnly;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final RawFilter getRawFilter()
+  {
+    return rawFilter;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setRawFilter(RawFilter rawFilter)
+  {
+    this.rawFilter = rawFilter;
+
+    filter = null;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final SearchFilter getFilter()
+  {
+    try
+    {
+      if (filter == null)
+      {
+        filter = rawFilter.toSearchFilter();
+      }
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      setResultCode(de.getResultCode());
+      appendErrorMessage(de.getErrorMessage());
+      setMatchedDN(de.getMatchedDN());
+      setReferralURLs(de.getReferralURLs());
+    }
+    return filter;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final LinkedHashSet<String> getAttributes()
+  {
+    return attributes;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setAttributes(LinkedHashSet<String> attributes)
+  {
+    if (attributes == null)
+    {
+      this.attributes.clear();
+    }
+    else
+    {
+      this.attributes = attributes;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final int getEntriesSent()
+  {
+    return entriesSent;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final int getReferencesSent()
+  {
+    return referencesSent;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean returnEntry(Entry entry, List<Control> controls)
+  {
+    boolean typesOnly = getTypesOnly();
+    // See if the operation has been abandoned.  If so, then don't send the
+    // entry and indicate that the search should end.
+    if (getCancelRequest() != null)
+    {
+      setResultCode(ResultCode.CANCELED);
+      return false;
+    }
+
+    // See if the size limit has been exceeded.  If so, then don't send the
+    // entry and indicate that the search should end.
+    if ((getSizeLimit() > 0) && (getEntriesSent() >= getSizeLimit()))
+    {
+      setResultCode(ResultCode.SIZE_LIMIT_EXCEEDED);
+      appendErrorMessage(getMessage(MSGID_SEARCH_SIZE_LIMIT_EXCEEDED,
+                                    getSizeLimit()));
+      return false;
+    }
+
+    // See if the time limit has expired.  If so, then don't send the entry and
+    // indicate that the search should end.
+    if ((getTimeLimit() > 0) && (TimeThread.getTime() >=
+                                                getTimeLimitExpiration()))
+    {
+      setResultCode(ResultCode.TIME_LIMIT_EXCEEDED);
+      appendErrorMessage(getMessage(MSGID_SEARCH_TIME_LIMIT_EXCEEDED,
+                                    getTimeLimit()));
+      return false;
+    }
+
+    // Determine whether the provided entry is a subentry and if so whether it
+    // should be returned.
+    if ((getScope() != SearchScope.BASE_OBJECT) &&
+        (! isReturnLDAPSubentries()) &&
+        entry.isLDAPSubentry())
+    {
+      // Check to see if the filter contains an equality element with the
+      // objectclass attribute type and a value of "ldapSubentry".  If so, then
+      // we'll return it anyway.  Technically, this isn't part of the
+      // specification so we don't need to get carried away with really in-depth
+      // checks.
+      SearchFilter filter = getFilter();
+      switch (filter.getFilterType())
+      {
+        case AND:
+        case OR:
+          for (SearchFilter f : filter.getFilterComponents())
+          {
+            if ((f.getFilterType() == FilterType.EQUALITY) &&
+                (f.getAttributeType().isObjectClassType()))
+            {
+              AttributeValue v = f.getAssertionValue();
+              if (toLowerCase(v.getStringValue()).equals("ldapsubentry"))
+              {
+                setReturnLDAPSubentries(true);
+              }
+              break;
+            }
+          }
+          break;
+        case EQUALITY:
+          AttributeType t = filter.getAttributeType();
+          if (t.isObjectClassType())
+          {
+            AttributeValue v = filter.getAssertionValue();
+            if (toLowerCase(v.getStringValue()).equals("ldapsubentry"))
+            {
+              setReturnLDAPSubentries(true);
+            }
+          }
+          break;
+      }
+
+      if (! isReturnLDAPSubentries())
+      {
+        // We still shouldn't return it even based on the filter.  Just throw it
+        // away without doing anything.
+        return true;
+      }
+    }
+
+
+    // Determine whether to include the account usable control.  If so, then
+    // create it now.
+    if (isIncludeUsableControl())
+    {
+      try
+      {
+        // FIXME -- Need a way to enable PWP debugging.
+        PasswordPolicyState pwpState = new PasswordPolicyState(entry, false,
+                                                               false);
+
+        boolean isInactive           = pwpState.isDisabled() ||
+                                       pwpState.isAccountExpired();
+        boolean isLocked             = pwpState.lockedDueToFailures() ||
+                                       pwpState.lockedDueToMaximumResetAge() ||
+                                       pwpState.lockedDueToIdleInterval();
+        boolean isReset              = pwpState.mustChangePassword();
+        boolean isExpired            = pwpState.isPasswordExpired();
+
+        if (isInactive || isLocked || isReset || isExpired)
+        {
+          int secondsBeforeUnlock  = pwpState.getSecondsUntilUnlock();
+          int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
+
+          if (controls == null)
+          {
+            controls = new ArrayList<Control>(1);
+          }
+
+          controls.add(new AccountUsableResponseControl(isInactive, isReset,
+                                isExpired, remainingGraceLogins, isLocked,
+                                secondsBeforeUnlock));
+        }
+        else
+        {
+          if (controls == null)
+          {
+            controls = new ArrayList<Control>(1);
+          }
+
+          int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration();
+          controls.add(new AccountUsableResponseControl(
+                                secondsBeforeExpiration));
+        }
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+      }
+    }
+
+    // Check to see if the entry can be read by the client.
+    SearchResultEntry tmpSearchEntry = new SearchResultEntry(entry,
+        controls);
+    if (AccessControlConfigManager.getInstance()
+        .getAccessControlHandler().maySend(this, tmpSearchEntry) == false) {
+      return true;
+    }
+
+    // Make a copy of the entry and pare it down to only include the set
+    // of
+    // requested attributes.
+    Entry entryToReturn;
+    if ((getAttributes() == null) || getAttributes().isEmpty())
+    {
+      entryToReturn = entry.duplicateWithoutOperationalAttributes(typesOnly,
+                                                                  true);
+    }
+    else
+    {
+      entryToReturn = entry.duplicateWithoutAttributes();
+
+      for (String attrName : getAttributes())
+      {
+        if (attrName.equals("*"))
+        {
+          // This is a special placeholder indicating that all user attributes
+          // should be returned.
+          if (typesOnly)
+          {
+            // First, add the placeholder for the objectclass attribute.
+            AttributeType ocType =
+                 DirectoryServer.getObjectClassAttributeType();
+            List<Attribute> ocList = new ArrayList<Attribute>(1);
+            ocList.add(new Attribute(ocType));
+            entryToReturn.putAttribute(ocType, ocList);
+          }
+          else
+          {
+            // First, add the objectclass attribute.
+            Attribute ocAttr = entry.getObjectClassAttribute();
+            try
+            {
+              entryToReturn.setObjectClasses(ocAttr.getValues());
+            }
+            catch (DirectoryException e)
+            {
+              // We cannot get this exception because the object classes have
+              // already been validated in the entry they came from.
+            }
+          }
+
+          // Next iterate through all the user attributes and include them.
+          for (AttributeType t : entry.getUserAttributes().keySet())
+          {
+            List<Attribute> attrList =
+                 entry.duplicateUserAttribute(t, null, typesOnly);
+            entryToReturn.putAttribute(t, attrList);
+          }
+
+          continue;
+        }
+        else if (attrName.equals("+"))
+        {
+          // This is a special placeholder indicating that all operational
+          // attributes should be returned.
+          for (AttributeType t : entry.getOperationalAttributes().keySet())
+          {
+            List<Attribute> attrList =
+                 entry.duplicateOperationalAttribute(t, null, typesOnly);
+            entryToReturn.putAttribute(t, attrList);
+          }
+
+          continue;
+        }
+
+        String lowerName;
+        HashSet<String> options;
+        int semicolonPos = attrName.indexOf(';');
+        if (semicolonPos > 0)
+        {
+          lowerName = toLowerCase(attrName.substring(0, semicolonPos));
+          int nextPos = attrName.indexOf(';', semicolonPos+1);
+          options = new HashSet<String>();
+          while (nextPos > 0)
+          {
+            options.add(attrName.substring(semicolonPos+1, nextPos));
+
+            semicolonPos = nextPos;
+            nextPos = attrName.indexOf(';', semicolonPos+1);
+          }
+
+          options.add(attrName.substring(semicolonPos+1));
+        }
+        else
+        {
+          lowerName = toLowerCase(attrName);
+          options = null;
+        }
+
+
+        AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
+        if (attrType == null)
+        {
+          boolean added = false;
+          for (AttributeType t : entry.getUserAttributes().keySet())
+          {
+            if (t.hasNameOrOID(lowerName))
+            {
+              List<Attribute> attrList =
+                   entry.duplicateUserAttribute(t, options, typesOnly);
+              if (attrList != null)
+              {
+                entryToReturn.putAttribute(t, attrList);
+
+                added = true;
+                break;
+              }
+            }
+          }
+
+          if (added)
+          {
+            continue;
+          }
+
+          for (AttributeType t : entry.getOperationalAttributes().keySet())
+          {
+            if (t.hasNameOrOID(lowerName))
+            {
+              List<Attribute> attrList =
+                   entry.duplicateOperationalAttribute(t, options, typesOnly);
+              if (attrList != null)
+              {
+                entryToReturn.putAttribute(t, attrList);
+
+                break;
+              }
+            }
+          }
+        }
+        else
+        {
+          if (attrType.isObjectClassType()) {
+            if (typesOnly)
+            {
+              AttributeType ocType =
+                   DirectoryServer.getObjectClassAttributeType();
+              List<Attribute> ocList = new ArrayList<Attribute>(1);
+              ocList.add(new Attribute(ocType));
+              entryToReturn.putAttribute(ocType, ocList);
+            }
+            else
+            {
+              List<Attribute> attrList = new ArrayList<Attribute>(1);
+              attrList.add(entry.getObjectClassAttribute());
+              entryToReturn.putAttribute(attrType, attrList);
+            }
+          }
+          else
+          {
+            List<Attribute> attrList =
+                 entry.duplicateOperationalAttribute(attrType, options,
+                                                     typesOnly);
+            if (attrList == null)
+            {
+              attrList = entry.duplicateUserAttribute(attrType, options,
+                                                      typesOnly);
+            }
+            if (attrList != null)
+            {
+              entryToReturn.putAttribute(attrType, attrList);
+            }
+          }
+        }
+      }
+    }
+
+    if (isRealAttributesOnly())
+    {
+      entryToReturn.stripVirtualAttributes();
+    }
+    else if (isVirtualAttributesOnly())
+    {
+      entryToReturn.stripRealAttributes();
+    }
+
+    // If there is a matched values control, then further pare down the entry
+    // based on the filters that it contains.
+    MatchedValuesControl matchedValuesControl = getMatchedValuesControl();
+    if ((matchedValuesControl != null) && (! typesOnly))
+    {
+      // First, look at the set of objectclasses.
+      AttributeType attrType = DirectoryServer.getObjectClassAttributeType();
+      Iterator<String> ocIterator =
+           entryToReturn.getObjectClasses().values().iterator();
+      while (ocIterator.hasNext())
+      {
+        String ocName = ocIterator.next();
+        AttributeValue v = new AttributeValue(attrType,
+                                              new ASN1OctetString(ocName));
+        if (! matchedValuesControl.valueMatches(attrType, v))
+        {
+          ocIterator.remove();
+        }
+      }
+
+
+      // Next, the set of user attributes.
+      for (AttributeType t : entryToReturn.getUserAttributes().keySet())
+      {
+        for (Attribute a : entryToReturn.getUserAttribute(t))
+        {
+          Iterator<AttributeValue> valueIterator = a.getValues().iterator();
+          while (valueIterator.hasNext())
+          {
+            AttributeValue v = valueIterator.next();
+            if (! matchedValuesControl.valueMatches(t, v))
+            {
+              valueIterator.remove();
+            }
+          }
+        }
+      }
+
+
+      // Then the set of operational attributes.
+      for (AttributeType t : entryToReturn.getOperationalAttributes().keySet())
+      {
+        for (Attribute a : entryToReturn.getOperationalAttribute(t))
+        {
+          Iterator<AttributeValue> valueIterator = a.getValues().iterator();
+          while (valueIterator.hasNext())
+          {
+            AttributeValue v = valueIterator.next();
+            if (! matchedValuesControl.valueMatches(t, v))
+            {
+              valueIterator.remove();
+            }
+          }
+        }
+      }
+    }
+
+
+    // Convert the provided entry to a search result entry.
+    SearchResultEntry searchEntry = new SearchResultEntry(entryToReturn,
+                                                          controls);
+
+    // Strip out any attributes that the client does not have access to.
+
+    // FIXME: need some way to prevent plugins from adding attributes or
+    // values that the client is not permitted to see.
+    searchEntry = AccessControlConfigManager.getInstance()
+        .getAccessControlHandler().filterEntry(this, searchEntry);
+
+    // Invoke any search entry plugins that may be registered with the server.
+    SearchEntryPluginResult pluginResult =
+         DirectoryServer.getPluginConfigManager().
+              invokeSearchResultEntryPlugins(this, searchEntry);
+    if (pluginResult.connectionTerminated())
+    {
+      // We won't attempt to send this entry, and we won't continue with
+      // any processing.  Just update the operation to indicate that it was
+      // cancelled and return false.
+      setResultCode(ResultCode.CANCELED);
+      appendErrorMessage(getMessage(MSGID_CANCELED_BY_SEARCH_ENTRY_DISCONNECT,
+                                    String.valueOf(entry.getDN())));
+      return false;
+    }
+
+
+    // Send the entry to the client.
+    if (pluginResult.sendEntry())
+    {
+      try
+      {
+        sendSearchEntry(searchEntry);
+        // Log the entry sent to the client.
+        logSearchResultEntry(this, searchEntry);
+
+        incrementEntriesSent();
+      }
+      catch (DirectoryException de)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+
+        setResponseData(de);
+        return false;
+      }
+    }
+
+    return pluginResult.continueSearch();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean returnReference(SearchResultReference reference)
+  {
+    // See if the operation has been abandoned.  If so, then don't send the
+    // reference and indicate that the search should end.
+    if (getCancelRequest() != null)
+    {
+      setResultCode(ResultCode.CANCELED);
+      return false;
+    }
+
+
+    // See if the time limit has expired.  If so, then don't send the entry and
+    // indicate that the search should end.
+    if ((getTimeLimit() > 0) && (TimeThread.getTime() >=
+                                        getTimeLimitExpiration()))
+    {
+      setResultCode(ResultCode.TIME_LIMIT_EXCEEDED);
+      appendErrorMessage(getMessage(MSGID_SEARCH_TIME_LIMIT_EXCEEDED,
+                                    getTimeLimit()));
+      return false;
+    }
+
+
+    // See if we know that this client can't handle referrals.  If so, then
+    // don't even try to send it.
+    if (! isClientAcceptsReferrals())
+    {
+      return true;
+    }
+
+
+    // See if the client has permission to read this reference.
+    if (AccessControlConfigManager.getInstance()
+        .getAccessControlHandler().maySend(this, reference) == false) {
+      return true;
+    }
+
+
+    // Invoke any search reference plugins that may be registered with the
+    // server.
+    SearchReferencePluginResult pluginResult =
+         DirectoryServer.getPluginConfigManager().
+              invokeSearchResultReferencePlugins(this, reference);
+    if (pluginResult.connectionTerminated())
+    {
+      // We won't attempt to send this entry, and we won't continue with
+      // any processing.  Just update the operation to indicate that it was
+      // cancelled and return false.
+      setResultCode(ResultCode.CANCELED);
+      appendErrorMessage(getMessage(MSGID_CANCELED_BY_SEARCH_REF_DISCONNECT,
+           String.valueOf(reference.getReferralURLString())));
+      return false;
+    }
+
+
+    // Send the reference to the client.  Note that this could throw an
+    // exception, which would indicate that the associated client can't handle
+    // referrals.  If that't the case, then set a flag so we'll know not to try
+    // to send any more.
+    if (pluginResult.sendReference())
+    {
+      try
+      {
+        if (sendSearchReference(reference))
+        {
+          // Log the entry sent to the client.
+          logSearchResultReference(this, reference);
+          incrementReferencesSent();
+
+          // FIXME -- Should the size limit apply here?
+        }
+        else
+        {
+          // We know that the client can't handle referrals, so we won't try to
+          // send it any more.
+          setClientAcceptsReferrals(false);
+        }
+      }
+      catch (DirectoryException de)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+
+        setResponseData(de);
+        return false;
+      }
+    }
+
+    return pluginResult.continueSearch();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void sendSearchResultDone()
+  {
+    // Send the search result done message to the client.  We want to make sure
+    // that this only gets sent once, and it's possible that this could be
+    // multithreaded in the event of a persistent search, so do it safely.
+    if (responseSent.compareAndSet(false, true))
+    {
+      // Send the response to the client.
+      clientConnection.sendResponse(this);
+
+      // Log the search result.
+      logSearchResultDone(this);
+
+
+      // Invoke the post-response search plugins.
+      DirectoryServer.getPluginConfigManager().
+           invokePostResponseSearchPlugins(this);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final OperationType getOperationType()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    return OperationType.SEARCH;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void disconnectClient(DisconnectReason disconnectReason,
+                                     boolean sendNotification, String message,
+                                     int messageID)
+  {
+    // Before calling clientConnection.disconnect, we need to mark this
+    // operation as cancelled so that the attempt to cancel it later won't cause
+    // an unnecessary delay.
+    setCancelResult(CancelResult.CANCELED);
+
+    clientConnection.disconnect(disconnectReason, sendNotification, message,
+                                messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final String[][] getRequestLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    String attrs;
+    if ((attributes == null) || attributes.isEmpty())
+    {
+      attrs = null;
+    }
+    else
+    {
+      StringBuilder attrBuffer = new StringBuilder();
+      Iterator<String> iterator = attributes.iterator();
+      attrBuffer.append(iterator.next());
+
+      while (iterator.hasNext())
+      {
+        attrBuffer.append(", ");
+        attrBuffer.append(iterator.next());
+      }
+
+      attrs = attrBuffer.toString();
+    }
+
+    return new String[][]
+    {
+      new String[] { LOG_ELEMENT_BASE_DN, String.valueOf(rawBaseDN) },
+      new String[] { LOG_ELEMENT_SCOPE, String.valueOf(scope) },
+      new String[] { LOG_ELEMENT_SIZE_LIMIT, String.valueOf(sizeLimit) },
+      new String[] { LOG_ELEMENT_TIME_LIMIT, String.valueOf(timeLimit) },
+      new String[] { LOG_ELEMENT_FILTER, String.valueOf(rawFilter) },
+      new String[] { LOG_ELEMENT_REQUESTED_ATTRIBUTES, attrs }
+    };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final String[][] getResponseLogElements()
+  {
+    // Note that no debugging will be done in this method because it is a likely
+    // candidate for being called by the logging subsystem.
+
+    String resultCode = String.valueOf(getResultCode().getIntValue());
+
+    String errorMessage;
+    StringBuilder errorMessageBuffer = getErrorMessage();
+    if (errorMessageBuffer == null)
+    {
+      errorMessage = null;
+    }
+    else
+    {
+      errorMessage = errorMessageBuffer.toString();
+    }
+
+    String matchedDNStr;
+    DN matchedDN = getMatchedDN();
+    if (matchedDN == null)
+    {
+      matchedDNStr = null;
+    }
+    else
+    {
+      matchedDNStr = matchedDN.toString();
+    }
+
+    String referrals;
+    List<String> referralURLs = getReferralURLs();
+    if ((referralURLs == null) || referralURLs.isEmpty())
+    {
+      referrals = null;
+    }
+    else
+    {
+      StringBuilder buffer = new StringBuilder();
+      Iterator<String> iterator = referralURLs.iterator();
+      buffer.append(iterator.next());
+
+      while (iterator.hasNext())
+      {
+        buffer.append(", ");
+        buffer.append(iterator.next());
+      }
+
+      referrals = buffer.toString();
+    }
+
+    String processingTime =
+         String.valueOf(processingStopTime - processingStartTime);
+
+    return new String[][]
+    {
+      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
+      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
+      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
+      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
+      new String[] { LOG_ELEMENT_ENTRIES_SENT, String.valueOf(entriesSent) },
+      new String[] { LOG_ELEMENT_REFERENCES_SENT,
+                     String.valueOf(referencesSent ) },
+      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
+    };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return proxiedAuthorizationDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final List<Control> getResponseControls()
+  {
+    return responseControls;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void addResponseControl(Control control)
+  {
+    responseControls.add(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void removeResponseControl(Control control)
+  {
+    responseControls.remove(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final CancelResult cancel(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+
+    if (persistentSearch != null)
+    {
+      DirectoryServer.deregisterPersistentSearch(persistentSearch);
+      persistentSearch = null;
+    }
+
+    CancelResult cancelResult = getCancelResult();
+    long stopWaitingTime = System.currentTimeMillis() + 5000;
+    while ((cancelResult == null) &&
+           (System.currentTimeMillis() < stopWaitingTime))
+    {
+      try
+      {
+        Thread.sleep(50);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+      }
+
+      cancelResult = getCancelResult();
+    }
+
+    if (cancelResult == null)
+    {
+      // This can happen in some rare cases (e.g., if a client disconnects and
+      // there is still a lot of data to send to that client), and in this case
+      // we'll prevent the cancel thread from blocking for a long period of
+      // time.
+      cancelResult = CancelResult.CANNOT_CANCEL;
+    }
+
+    return cancelResult;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final CancelRequest getCancelRequest()
+  {
+    return cancelRequest;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public
+  boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    this.cancelRequest = cancelRequest;
+    return true;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public final void toString(StringBuilder buffer)
+  {
+    buffer.append("SearchOperation(connID=");
+    buffer.append(clientConnection.getConnectionID());
+    buffer.append(", opID=");
+    buffer.append(operationID);
+    buffer.append(", baseDN=");
+    buffer.append(rawBaseDN);
+    buffer.append(", scope=");
+    buffer.append(scope.toString());
+    buffer.append(", filter=");
+    buffer.append(rawFilter.toString());
+    buffer.append(")");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setTimeLimitExpiration(Long timeLimitExpiration){
+    this.timeLimitExpiration = timeLimitExpiration;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isReturnLDAPSubentries()
+  {
+    return returnLDAPSubentries;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setReturnLDAPSubentries(boolean returnLDAPSubentries)
+  {
+    this.returnLDAPSubentries = returnLDAPSubentries;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public MatchedValuesControl getMatchedValuesControl()
+  {
+    return matchedValuesControl;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setMatchedValuesControl(MatchedValuesControl controls)
+  {
+    this.matchedValuesControl = controls;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public PersistentSearch getPersistentSearch()
+  {
+    return persistentSearch;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isIncludeUsableControl()
+  {
+    return includeUsableControl;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setIncludeUsableControl(boolean includeUsableControl)
+  {
+    this.includeUsableControl = includeUsableControl;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setPersistentSearch(PersistentSearch psearch)
+  {
+    this.persistentSearch = psearch;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Long getTimeLimitExpiration()
+  {
+    return timeLimitExpiration;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isClientAcceptsReferrals()
+  {
+    return clientAcceptsReferrals;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setClientAcceptsReferrals(boolean clientAcceptReferrals)
+  {
+    this.clientAcceptsReferrals = clientAcceptReferrals;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void incrementEntriesSent()
+  {
+    entriesSent++;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void incrementReferencesSent()
+  {
+    referencesSent++;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isSendResponse()
+  {
+    return sendResponse;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSendResponse(boolean sendResponse)
+  {
+    this.sendResponse = sendResponse;
+
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isRealAttributesOnly()
+  {
+    return this.realAttributesOnly;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isVirtualAttributesOnly()
+  {
+    return this.virtualAttributesOnly;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRealAttributesOnly(boolean realAttributesOnly)
+  {
+    this.realAttributesOnly = realAttributesOnly;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setVirtualAttributesOnly(boolean virtualAttributesOnly)
+  {
+    this.virtualAttributesOnly = virtualAttributesOnly;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void sendSearchEntry(SearchResultEntry searchEntry)
+    throws DirectoryException
+    {
+    getClientConnection().sendSearchEntry(this, searchEntry);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean sendSearchReference(SearchResultReference searchReference)
+  throws DirectoryException
+  {
+    return getClientConnection().sendSearchReference(this, searchReference);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
+  {
+    this.proxiedAuthorizationDN = proxiedAuthorizationDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void run()
+  {
+    setResultCode(ResultCode.UNDEFINED);
+    setSendResponse(true);
+
+    // Get the plugin config manager that will be used for invoking plugins.
+    PluginConfigManager pluginConfigManager =
+      DirectoryServer.getPluginConfigManager();
+
+
+    // Start the processing timer.
+    setProcessingStartTime();
+    int timeLimit = getTimeLimit();
+    Long timeLimitExpiration;
+    if (timeLimit <= 0)
+    {
+      timeLimitExpiration = Long.MAX_VALUE;
+    }
+    else
+    {
+      // FIXME -- Factor in the user's effective time limit.
+      timeLimitExpiration =
+        getProcessingStartTime() + (1000L * timeLimit);
+    }
+    setTimeLimitExpiration(timeLimitExpiration);
+
+    // Check for and handle a request to cancel this operation.
+    if (getCancelRequest() != null)
+    {
+      indicateCancelled(getCancelRequest());
+      setProcessingStopTime();
+      logSearchResultDone(this);
+      return;
+    }
+
+    // Create a labeled block of code that we can break out of if a problem is
+    // detected.
+    searchProcessing:
+    {
+      PreParsePluginResult preParseResult =
+        pluginConfigManager.invokePreParseSearchPlugins(this);
+      if (preParseResult.connectionTerminated())
+      {
+        // There's no point in continuing with anything.  Log the request and
+        // result and return.
+        setResultCode(ResultCode.CANCELED);
+
+        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
+        appendErrorMessage(getMessage(msgID));
+
+        setProcessingStopTime();
+
+        logSearchRequest(this);
+        logSearchResultDone(this);
+        pluginConfigManager.invokePostResponseSearchPlugins(this);
+        return;
+      }
+      else if (preParseResult.sendResponseImmediately())
+      {
+        logSearchRequest(this);
+        break searchProcessing;
+      }
+      else if (preParseResult.skipCoreProcessing())
+      {
+        break searchProcessing;
+      }
+
+
+      // Log the search request message.
+      logSearchRequest(this);
+
+
+      // Check for and handle a request to cancel this operation.
+      if (getCancelRequest() != null)
+      {
+        indicateCancelled(getCancelRequest());
+        setProcessingStopTime();
+        logSearchResultDone(this);
+        pluginConfigManager.invokePostResponseSearchPlugins(this);
+        return;
+      }
+
+
+      // Process the search base and filter to convert them from their raw forms
+      // as provided by the client to the forms required for the rest of the
+      // search processing.
+      DN baseDN = getBaseDN();
+      if (baseDN == null){
+        break searchProcessing;
+      }
+
+
+      // Retrieve the network group attached to the client connection
+      // and get a workflow to process the operation.
+      NetworkGroup ng = getClientConnection().getNetworkGroup();
+      Workflow workflow = ng.getWorkflowCandidate(baseDN);
+      if (workflow == null)
+      {
+        // We have found no workflow for the requested base DN, just return
+        // a no such entry result code and stop the processing.
+        updateOperationErrMsgAndResCode();
+        break searchProcessing;
+      }
+      workflow.execute(this);
+    }
+
+    // Check for and handle a request to cancel this operation.
+    if ((getCancelRequest() != null) ||
+        (getCancelResult() == CancelResult.CANCELED))
+    {
+      indicateCancelled(getCancelRequest());
+      setProcessingStopTime();
+      logSearchResultDone(this);
+      pluginConfigManager.invokePostResponseSearchPlugins(this);
+      return;
+    }
+
+    // Indicate that it is now too late to attempt to cancel the operation.
+    setCancelResult(CancelResult.TOO_LATE);
+
+    // Stop the processing timer.
+    setProcessingStopTime();
+
+    // If everything is successful to this point and it is not a persistent
+    // search, then send the search result done message to the client.
+    // Otherwise, we'll want to make the size and time limit values unlimited
+    // to ensure that the remainder of the persistent search isn't subject to
+    // those restrictions.
+    if (isSendResponse())
+    {
+      sendSearchResultDone();
+    }
+    else
+    {
+      setSizeLimit(0);
+      setTimeLimit(0);
+    }
+  }
+
+  /**
+   * Updates the error message and the result code of the operation.
+   *
+   * This method is called because no workflows were found to process
+   * the operation.
+   */
+  private void updateOperationErrMsgAndResCode()
+  {
+    setResultCode(ResultCode.NO_SUCH_OBJECT);
+    appendErrorMessage(getMessage(MSGID_SEARCH_BASE_DOESNT_EXIST,
+                                  String.valueOf(getBaseDN())));
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationWrapper.java b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationWrapper.java
new file mode 100644
index 0000000..415da2e
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationWrapper.java
@@ -0,0 +1,903 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.controls.MatchedValuesControl;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.RawFilter;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.SearchResultReference;
+import org.opends.server.types.SearchScope;
+
+/**
+ * This abstract class wraps/decorates a given search operation.
+ * This class will be extended by sub-classes to enhance the
+ * functionnality of the SearchOperationBasis.
+ */
+public abstract class SearchOperationWrapper implements SearchOperation
+{
+  private SearchOperation search;
+
+  /**
+   * Creates a new search operation based on the provided search operation.
+   *
+   * @param search The search operation to wrap
+   */
+  protected SearchOperationWrapper(SearchOperation search){
+    this.search = search;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addRequestControl(Control control)
+  {
+    search.addRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addResponseControl(Control control)
+  {
+    search.addResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendAdditionalLogMessage(String message)
+  {
+    search.appendAdditionalLogMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void appendErrorMessage(String message)
+  {
+    search.appendErrorMessage(message);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult cancel(CancelRequest cancelRequest)
+  {
+    return search.cancel(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void disconnectClient(DisconnectReason disconnectReason,
+      boolean sendNotification, String message, int messageID)
+  {
+    search.disconnectClient(disconnectReason, sendNotification, message,
+        messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean dontSynchronize()
+  {
+    return search.dontSynchronize();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getAdditionalLogMessage()
+  {
+    return search.getAdditionalLogMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object getAttachment(String name)
+  {
+    return search.getAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<String, Object> getAttachments()
+  {
+    return search.getAttachments();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getAuthorizationDN()
+  {
+    return search.getAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry getAuthorizationEntry()
+  {
+    return search.getAuthorizationEntry();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelRequest getCancelRequest()
+  {
+    return search.getCancelRequest();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public CancelResult getCancelResult()
+  {
+    return search.getCancelResult();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ClientConnection getClientConnection()
+  {
+    return search.getClientConnection();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getCommonLogElements()
+  {
+    return search.getCommonLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getConnectionID()
+  {
+    return search.getConnectionID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public StringBuilder getErrorMessage()
+  {
+    return search.getErrorMessage();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getMatchedDN()
+  {
+    return search.getMatchedDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getMessageID()
+  {
+    return search.getMessageID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getOperationID()
+  {
+    return search.getOperationID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public OperationType getOperationType()
+  {
+    return search.getOperationType();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStartTime()
+  {
+    return search.getProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingStopTime()
+  {
+    return search.getProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long getProcessingTime()
+  {
+    return search.getProcessingTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<String> getReferralURLs()
+  {
+    return search.getReferralURLs();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getRequestControls()
+  {
+    return search.getRequestControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getRequestLogElements()
+  {
+    return search.getRequestLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public List<Control> getResponseControls()
+  {
+    return search.getResponseControls();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String[][] getResponseLogElements()
+  {
+    return search.getResponseLogElements();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ResultCode getResultCode()
+  {
+    return search.getResultCode();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void indicateCancelled(CancelRequest cancelRequest)
+  {
+    search.indicateCancelled(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isInternalOperation()
+  {
+    return search.isInternalOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isSynchronizationOperation()
+  {
+    return search.isSynchronizationOperation();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void operationCompleted()
+  {
+    search.operationCompleted();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object removeAttachment(String name)
+  {
+    return search.removeAttachment(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeRequestControl(Control control)
+  {
+    search.removeRequestControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeResponseControl(Control control)
+  {
+    search.removeResponseControl(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean returnEntry(Entry entry, List<Control> controls)
+  {
+    return search.returnEntry(entry, controls);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean returnReference(SearchResultReference reference)
+  {
+    return search.returnReference(reference);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAdditionalLogMessage(StringBuilder additionalLogMessage)
+  {
+    search.setAdditionalLogMessage(additionalLogMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Object setAttachment(String name, Object value)
+  {
+    return search.setAttachment(name, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttachments(Map<String, Object> attachments)
+  {
+    search.setAttachments(attachments);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAuthorizationEntry(Entry authorizationEntry)
+  {
+    search.setAuthorizationEntry(authorizationEntry);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean setCancelRequest(CancelRequest cancelRequest)
+  {
+    return search.setCancelRequest(cancelRequest);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setCancelResult(CancelResult cancelResult)
+  {
+    search.setCancelResult(cancelResult);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setDontSynchronize(boolean dontSynchronize)
+  {
+    search.setDontSynchronize(dontSynchronize);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setErrorMessage(StringBuilder errorMessage)
+  {
+    search.setErrorMessage(errorMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setInternalOperation(boolean isInternalOperation)
+  {
+    search.setInternalOperation(isInternalOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setMatchedDN(DN matchedDN)
+  {
+    search.setMatchedDN(matchedDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStartTime()
+  {
+    search.setProcessingStartTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProcessingStopTime()
+  {
+    search.setProcessingStopTime();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setReferralURLs(List<String> referralURLs)
+  {
+    search.setReferralURLs(referralURLs);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResponseData(DirectoryException directoryException)
+  {
+    search.setResponseData(directoryException);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setResultCode(ResultCode resultCode)
+  {
+    search.setResultCode(resultCode);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSynchronizationOperation(boolean isSynchronizationOperation)
+  {
+    search.setSynchronizationOperation(isSynchronizationOperation);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return search.toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void toString(StringBuilder buffer)
+  {
+    search.toString(buffer);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public LinkedHashSet<String> getAttributes()
+  {
+    return search.getAttributes();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getBaseDN()
+  {
+    return search.getBaseDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DereferencePolicy getDerefPolicy()
+  {
+    return search.getDerefPolicy();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getEntriesSent()
+  {
+    return search.getEntriesSent();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchFilter getFilter()
+  {
+    return search.getFilter();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getRawBaseDN()
+  {
+    return search.getRawBaseDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public RawFilter getRawFilter()
+  {
+    return search.getRawFilter();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getReferencesSent()
+  {
+    return search.getReferencesSent();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchScope getScope()
+  {
+    return search.getScope();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getSizeLimit()
+  {
+    return search.getSizeLimit();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getTimeLimit()
+  {
+    return search.getTimeLimit();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean getTypesOnly()
+  {
+    return search.getTypesOnly();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void sendSearchResultDone()
+  {
+    search.sendSearchResultDone();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setAttributes(LinkedHashSet<String> attributes)
+  {
+    search.setAttributes(attributes);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setBaseDN(DN baseDN)
+  {
+    search.setBaseDN(baseDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setDerefPolicy(DereferencePolicy derefPolicy)
+  {
+    search.setDerefPolicy(derefPolicy);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawBaseDN(ByteString rawBaseDN)
+  {
+    search.setRawBaseDN(rawBaseDN);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRawFilter(RawFilter rawFilter)
+  {
+    search.setRawFilter(rawFilter);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setScope(SearchScope scope)
+  {
+    search.setScope(scope);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSizeLimit(int sizeLimit)
+  {
+    search.setSizeLimit(sizeLimit);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setTimeLimit(int timeLimit)
+  {
+    search.setTimeLimit(timeLimit);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setTypesOnly(boolean typesOnly)
+  {
+    search.setTypesOnly(typesOnly);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setTimeLimitExpiration(Long timeLimitExpiration)
+  {
+    search.setTimeLimitExpiration(timeLimitExpiration);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isReturnLDAPSubentries()
+  {
+    return search.isReturnLDAPSubentries();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setReturnLDAPSubentries(boolean returnLDAPSubentries)
+  {
+    search.setReturnLDAPSubentries(returnLDAPSubentries);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public MatchedValuesControl getMatchedValuesControl()
+  {
+    return search.getMatchedValuesControl();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setMatchedValuesControl(MatchedValuesControl controls)
+  {
+    search.setMatchedValuesControl(controls);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isIncludeUsableControl()
+  {
+    return search.isIncludeUsableControl();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setIncludeUsableControl(boolean includeUsableControl)
+  {
+    search.setIncludeUsableControl(includeUsableControl);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setPersistentSearch(PersistentSearch psearch)
+  {
+    search.setPersistentSearch(psearch);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public PersistentSearch getPersistentSearch()
+  {
+    return search.getPersistentSearch();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Long getTimeLimitExpiration()
+  {
+    return search.getTimeLimitExpiration();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isClientAcceptsReferrals()
+  {
+    return search.isClientAcceptsReferrals();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setClientAcceptsReferrals(boolean clientAcceptReferrals)
+  {
+    search.setClientAcceptsReferrals(clientAcceptReferrals);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void incrementEntriesSent()
+  {
+    search.incrementEntriesSent();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void incrementReferencesSent()
+  {
+    search.incrementReferencesSent();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isSendResponse()
+  {
+    return search.isSendResponse();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setSendResponse(boolean sendResponse)
+  {
+    search.setSendResponse(sendResponse);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isRealAttributesOnly(){
+    return search.isRealAttributesOnly();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setRealAttributesOnly(boolean realAttributesOnly){
+    search.setRealAttributesOnly(realAttributesOnly);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isVirtualAttributesOnly(){
+    return search.isVirtualAttributesOnly();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setVirtualAttributesOnly(boolean virtualAttributesOnly){
+    search.setVirtualAttributesOnly(virtualAttributesOnly);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @throws DirectoryException
+   */
+  public void sendSearchEntry(SearchResultEntry entry)
+    throws DirectoryException
+    {
+    search.sendSearchEntry(entry);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @throws DirectoryException
+   */
+  public boolean sendSearchReference(SearchResultReference reference)
+    throws DirectoryException
+    {
+    return search.sendSearchReference(reference);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getProxiedAuthorizationDN()
+  {
+    return search.getProxiedAuthorizationDN();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN){
+    search.setProxiedAuthorizationDN(proxiedAuthorizationDN);
+  }
+
+}
\ No newline at end of file
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/UnbindOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/UnbindOperation.java
index 03b920a..44363ac 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/UnbindOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/UnbindOperation.java
@@ -32,11 +32,13 @@
 import java.util.List;
 
 import org.opends.server.api.ClientConnection;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.CancelRequest;
 import org.opends.server.types.CancelResult;
 import org.opends.server.types.Control;
 import org.opends.server.types.DisconnectReason;
-import org.opends.server.types.Operation;
 import org.opends.server.types.OperationType;
 import org.opends.server.types.operation.PostOperationUnbindOperation;
 import org.opends.server.types.operation.PreParseUnbindOperation;
@@ -52,19 +54,14 @@
  * between the client and the Directory Server.
  */
 public class UnbindOperation
-       extends Operation
+       extends AbstractOperation
        implements PreParseUnbindOperation, PostOperationUnbindOperation
 {
 
-
-
-  // The time that processing started on this operation.
-  private long processingStartTime;
-
-  // The time that processing ended on this operation.
-  private long processingStopTime;
-
-
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
 
   /**
    * Creates a new unbind operation with the provided information.
@@ -180,45 +177,13 @@
     // An unbind operation can never have a response, so just ignore this.
   }
 
-
-
   /**
-   * {@inheritDoc}
+   * Performs the work of actually processing this operation.  This
+   * should include all processing for the operation, including
+   * invoking plugins, logging messages, performing access control,
+   * managing synchronization, and any other work that might need to
+   * be done in the course of processing.
    */
-  @Override()
-  public long getProcessingStartTime()
-  {
-    return processingStartTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public long getProcessingStopTime()
-  {
-    return processingStopTime;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
-  public long getProcessingTime()
-  {
-    return (processingStopTime - processingStartTime);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override()
   public final void run()
   {
     // Get the plugin config manager that will be used for invoking plugins.
@@ -226,7 +191,7 @@
          DirectoryServer.getPluginConfigManager();
     boolean skipPostOperation = false;
 
-    processingStartTime = System.currentTimeMillis();
+    setProcessingStartTime();
 
 
     // Invoke the pre-parse unbind plugins.  We don't care about the result
@@ -250,7 +215,7 @@
     // Invoke the post-operation unbind plugins.
     pluginConfigManager.invokePostOperationUnbindPlugins(this);
 
-    processingStopTime = System.currentTimeMillis();
+    setProcessingStopTime();
   }
 
 
@@ -282,7 +247,7 @@
    * {@inheritDoc}
    */
   @Override()
-  protected boolean setCancelRequest(CancelRequest cancelRequest)
+  public boolean setCancelRequest(CancelRequest cancelRequest)
   {
     // Unbind operations cannot be canceled.
     return false;
@@ -302,5 +267,6 @@
     buffer.append(operationID);
     buffer.append(")");
   }
+
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/Workflow.java b/opendj-sdk/opends/src/server/org/opends/server/core/Workflow.java
new file mode 100644
index 0000000..baad2ba
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/Workflow.java
@@ -0,0 +1,73 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import org.opends.server.types.DN;
+import org.opends.server.types.Operation;
+
+
+/**
+ * This class defines the workflow interface. There can be two
+ * implementations for the workflows.
+ *
+ * In the first workflow implementation a workflow is a list of
+ * structured tasks (aka workflow element). Each task is working
+ * on a set of data being identified by a base DN. The order of the
+ * tasks and their synchronization are defined statically by a task
+ * tree.
+ *
+ * In the second workflow implementation each workflow is a node
+ * in a workflow tree (aka worflow topology). Each node in the tree
+ * is linked to a workflow object of the first implementation and the
+ * base DN of the node is the base DN of the attached workflow object.
+ * The relationship of the nodes in the tree is based on the base DNs
+ * of the nodes. A workflow node is a subordinate of another workflow
+ * node when the base DN of the former is a superior of the base DN of
+ * the latter. Workflow topology are useful, for example, in subtree
+ * searches: search is performed on a node as well as on all the
+ * subordinate nodes.
+ */
+public interface Workflow
+{
+  /**
+   * Gets the base DN which identifies the set of data upon which the
+   * workflow is to be executed.
+   *
+   * @return the base DN of the workflow
+   */
+  public DN getBaseDN();
+
+
+  /**
+   * Executes all the tasks defined by the workflow task tree for a given
+   * operation.
+   *
+   * @param operation  the operation to execute
+   */
+  public void execute(Operation operation);
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowImpl.java b/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowImpl.java
new file mode 100644
index 0000000..e811757
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowImpl.java
@@ -0,0 +1,127 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import org.opends.server.types.DN;
+import org.opends.server.types.Operation;
+import org.opends.server.workflowelement.WorkflowElement;
+
+
+/**
+ * This class implements the workflow interface. Each task in the workflow
+ * is implemented by a WorkflowElement. All the tasks in the workflow are
+ * structured in a tree of tasks and the root node of the task tree is
+ * stored in the Workflow class itself. To execute a workflow, one just need
+ * to call the execute method on the root node of the task tree. Then each
+ * task in turn will execute its subordinate nodes and synchronizes them
+ * as needed.
+ */
+public class WorkflowImpl implements Workflow
+{
+
+  // The root of the workflow task tree.
+  private WorkflowElement rootWorkflowElement = null;
+
+  // The base DN of the data handled by the workflow.
+  private DN baseDN = null;
+
+  // Flag indicating whether the workflow root node of the task tree is
+  // handling a private local backend.
+  //
+  // A private local backend is used by the server to store "private data"
+  // such as schemas, tasks, monitoring data, configuration data... Such
+  // private data are not returned upon a subtree search on the root DSE.
+  // Also it is not planned to have anything but a single node task tree
+  // to handle private local backend. So workflows used for proxy and
+  // virtual will always be made public (ie. not private). So, unless the
+  // rootWorkflowElement is handling a private local backend, the isPrivate
+  // flag will always return false.
+  private boolean isPrivate = false;
+
+
+  /**
+   * Creates a new instance of a workflow implementation. To define a worfklow
+   * one needs to provide a task tree root node (the rootWorkflowElement) and
+   * a base DN to identify the data set upon which the tasks can be applied.
+   *
+   * The rootWorkflowElement must not be null.
+   *
+   * @param baseDN              identifies the data handled by the workflow
+   * @param rootWorkflowElement the root node of the workflow task tree
+   */
+  public WorkflowImpl(
+      DN              baseDN,
+      WorkflowElement rootWorkflowElement
+      )
+  {
+    this.baseDN = baseDN;
+    this.rootWorkflowElement = rootWorkflowElement;
+    if (this.rootWorkflowElement != null)
+    {
+      this.isPrivate = rootWorkflowElement.isPrivate();
+    }
+  }
+
+
+  /**
+   * Gets the base DN of the data set being handled by the workflow.
+   *
+   * @return the workflow base DN
+   */
+  public DN getBaseDN()
+  {
+    return baseDN;
+  }
+
+
+  /**
+   * Indicates whether the root node of the workflow task tree is
+   * handling a private local backend.
+   *
+   * @return <code>true</code> if the workflow encapsulates a private local
+   *         backend
+   */
+  public boolean isPrivate()
+  {
+    return isPrivate;
+  }
+
+
+  /**
+   * Executes all the tasks defined by the workflow task tree for a given
+   * operation.
+   *
+   * @param operation  the operation to execute
+   */
+  public void execute(
+      Operation operation
+      )
+  {
+    rootWorkflowElement.execute(operation);
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowResultCode.java b/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowResultCode.java
new file mode 100644
index 0000000..28746ae
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowResultCode.java
@@ -0,0 +1,249 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import org.opends.server.types.ResultCode;
+
+
+/**
+ * This class implements the workflow result code. The workflow result code
+ * contains an LDAP result code along with an LDAP error message.
+ */
+public class WorkflowResultCode
+{
+  // The global result code.
+  private ResultCode resultCode = ResultCode.UNDEFINED;
+
+  // The global error message.
+  private StringBuilder errorMessage = new StringBuilder ("");
+
+
+  /**
+   * Creates a new instance of a workflow result. By default the result code
+   * is set to UNDEFINED and there is no error message.
+   */
+  public WorkflowResultCode()
+  {
+    // Nothing to implement.
+  }
+
+
+  /**
+   * Creates a new instance of a workflow result code and initializes it
+   * with a result code and an error message.
+   *
+   * @param resultCode    the initial value for the result code
+   * @param errorMessage  the initial value for the error message
+   */
+  public WorkflowResultCode(
+      ResultCode    resultCode,
+      StringBuilder errorMessage
+      )
+  {
+    this.resultCode   = resultCode;
+    this.errorMessage = errorMessage;
+  }
+
+
+  /**
+   * Elaborates a global result code. A workflow may execute an operation
+   * on several subordinate workflows. In such case, the parent workflow
+   * has to take into account all the subordinate result codes to elaborate
+   * a global result code.
+   *
+   * Sometimes, a referral result code has to be turned into a reference
+   * entry. When such case is occurring the elaborateGlobalResultCode method
+   * will return true.
+   *
+   * The global result code is elaborated as follows:
+   *
+   * <PRE>
+   *  -----------+------------+------------+-------------------------------
+   *  new        | current    | resulting  |
+   *  resultCode | resultCode | resultCode | action
+   *  -----------+------------+------------+-------------------------------
+   *  SUCCESS      NO_SUCH_OBJ  SUCCESS      -
+   *               REFERRAL     SUCCESS      send reference entry to client
+   *               other        [unchanged]  -
+   *  ---------------------------------------------------------------------
+   *  NO_SUCH_OBJ  SUCCESS      [unchanged]  -
+   *               REFERRAL     [unchanged]  -
+   *               other        [unchanged]  -
+   *  ---------------------------------------------------------------------
+   *  REFERRAL     SUCCESS      [unchanged]  send reference entry to client
+   *               REFERRAL     SUCCESS      send reference entry to client
+   *               NO_SUCH_OBJ  REFERRAL     -
+   *               other        [unchanged]  send reference entry to client
+   *  ---------------------------------------------------------------------
+   *  others       SUCCESS      other        -
+   *               REFERRAL     other        send reference entry to client
+   *               NO_SUCH_OBJ  other        -
+   *               other2       [unchanged]  -
+   *  ---------------------------------------------------------------------
+   * </PRE>
+   *
+   * @param newResultCode    the new result code to take into account
+   * @param newErrorMessage  the new error message associated to the new
+   *                         error code
+   * @return <code>true</code> if a referral result code must be turned
+   *         into a reference entry
+   */
+  public boolean elaborateGlobalResultCode(
+      ResultCode    newResultCode,
+      StringBuilder newErrorMessage
+      )
+  {
+    // Returned value
+    boolean sendReferenceEntry = false;
+
+    // if global result code has not been set yet then just take the new
+    // result code as is
+    if (resultCode == ResultCode.UNDEFINED)
+    {
+      resultCode   = newResultCode;
+      errorMessage = new StringBuilder (newErrorMessage);
+    }
+    else
+    {
+      // Elaborate the new result code (see table in the description header).
+
+      switch (newResultCode)
+      {
+      case SUCCESS:
+        //
+        // Received SUCCESS
+        // ----------------
+        //
+        switch (resultCode)
+        {
+          case NO_SUCH_OBJECT:
+            resultCode = ResultCode.SUCCESS;
+            errorMessage = new StringBuilder ("");
+            break;
+          case REFERRAL:
+            resultCode = ResultCode.SUCCESS;
+            errorMessage = new StringBuilder ("");
+            sendReferenceEntry = true;
+            break;
+          default:
+            // global resultCode remains the same
+            break;
+        }
+        break;
+      case NO_SUCH_OBJECT:
+        //
+        // Received NO SUCH OBJECT
+        // -----------------------
+        //
+        // global resultCode remains the same
+        break;
+      case REFERRAL:
+        //
+        // Received REFERRAL
+        // -----------------
+        //
+        switch (resultCode)
+        {
+          case REFERRAL:
+            resultCode = ResultCode.SUCCESS;
+            errorMessage = new StringBuilder ("");
+            sendReferenceEntry = true;
+            break;
+          case NO_SUCH_OBJECT:
+            resultCode = ResultCode.REFERRAL;
+            errorMessage = new StringBuilder (newErrorMessage);
+            break;
+          default:
+            // global resultCode remains the same
+            sendReferenceEntry = true;
+            break;
+        }
+        break;
+      default:
+        //
+        // Received other result codes
+        // ---------------------------
+        //
+        switch (resultCode)
+        {
+          case REFERRAL:
+            resultCode = newResultCode;
+            errorMessage = new StringBuilder (newErrorMessage);
+            sendReferenceEntry = true;
+            break;
+          case SUCCESS:
+            resultCode = newResultCode;
+            errorMessage = new StringBuilder (newErrorMessage);
+            break;
+          case NO_SUCH_OBJECT:
+            resultCode = newResultCode;
+            errorMessage = new StringBuilder (newErrorMessage);
+            break;
+          default:
+            // global resultCode remains the same but append the new
+            // error message into the current error message
+            if (errorMessage == null)
+            {
+              errorMessage =  new StringBuilder (newErrorMessage);
+            }
+            else
+            {
+              errorMessage.append(newErrorMessage);
+            }
+            break;
+        }
+        break;
+      }
+    }
+
+    return sendReferenceEntry;
+  }
+
+
+  /**
+   * Returns the global result code.
+   *
+   * @return the global result code.
+   */
+  public ResultCode resultCode()
+  {
+    return resultCode;
+  }
+
+
+  /**
+   * Returns the global error message.
+   *
+   * @return the global error message.
+   */
+  public StringBuilder errorMessage()
+  {
+    return errorMessage;
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowTopology.java b/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowTopology.java
new file mode 100644
index 0000000..a7543ec
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowTopology.java
@@ -0,0 +1,135 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import org.opends.server.types.DN;
+import org.opends.server.types.SearchScope;
+
+
+/**
+ * This class is the base class used to build the workflow topology.
+ * A workflow topology is a tree of workflows. Each node in the tree
+ * is attached to a WorkflowImpl which contains the task tree (ie. the
+ * processing).
+ *
+ * There are two types of workflow nodes. The first one is used to build
+ * nodes in the workflow topology (WorkflowTopologyNode) and the second
+ * one is used to implement the root DSE node (RootDseWorkflowTopology).
+ */
+
+public abstract class WorkflowTopology implements Workflow
+{
+  // The workflow implementation containing the task tree (ie. the processing)
+  private WorkflowImpl workflowImpl = null;
+
+
+  /**
+   * Each workflow node may have specific tasks to be executed before
+   * the workflow task tree. The tasks to execute before are stored in
+   * the following array, which is empty at the moment (implementation
+   * will come later on when needed).
+   */
+  // private WorkflowElement[] preWorkflowElements = null;
+
+
+  /**
+   * Each workflow node may have specific tasks to be executed after
+   * the workflow task tree. The tasks to execute after are stored in
+   * the following array, which is empty at the moment (implementation
+   * will come later on when needed).
+   */
+  // private WorkflowElement[] postWorkflowElements = null;
+
+
+  /**
+   * Create a new instance of the workflow topology base class.
+   * The instance is initialized with the workflow implementation which
+   * contains the task tree (ie. the processing).
+   *
+   * @param workflowImpl the workflow which contains the processing
+   */
+  protected WorkflowTopology(WorkflowImpl workflowImpl)
+  {
+    this.workflowImpl = workflowImpl;
+  }
+
+
+  /**
+   * Returns the workflow implementation which contains the task tree
+   * (ie. the processing).
+   *
+   * @return the workflow implementation which contains the processing
+   */
+  public WorkflowImpl getWorkflowImpl()
+  {
+    return workflowImpl;
+  }
+
+
+  /**
+   * Gets the base DN of the workflow node. The base DN of the workflow
+   * node is the base DN of the attached workflow implementation containing
+   * the processing.
+   *
+   * @return the base DN of the workflow containing the processing.
+   */
+  public DN getBaseDN()
+  {
+    return getWorkflowImpl().getBaseDN();
+  }
+
+
+  /**
+   * Elaborates a new search scope according to the current search scope.
+   * The new scope is intended to be used for searches on subordinate
+   * workflows.
+   *
+   * @param currentScope  the current search scope
+   * @return the new scope to use for searches on subordinate workflows,
+   *         <code>null</code> when current scope is 'base'
+   */
+
+  protected SearchScope elaborateScopeForSearchInSubordinates(
+      SearchScope currentScope
+      )
+  {
+    switch (currentScope)
+    {
+    case BASE_OBJECT:
+      return null;
+    case SINGLE_LEVEL:
+      return SearchScope.BASE_OBJECT;
+    case SUBORDINATE_SUBTREE:
+    case WHOLE_SUBTREE:
+      return SearchScope.WHOLE_SUBTREE;
+    default:
+      return currentScope;
+    }
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowTopologyNode.java b/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowTopologyNode.java
new file mode 100644
index 0000000..cc93f7a
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/WorkflowTopologyNode.java
@@ -0,0 +1,535 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import java.util.ArrayList;
+
+import org.opends.server.types.DN;
+import org.opends.server.types.Operation;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.SearchScope;
+import org.opends.server.workflowelement.WorkflowElement;
+
+
+/**
+ * This class implements a workflow node. A workflow node is used
+ * to build a tree of workflows (aka workflow topology). Each node
+ * may have a parent node and/or subordinate nodes. A node with no
+ * parent is a naming context.
+ *
+ * Each node in the workflow topology is linked to a WorkflowImpl
+ * which contains the real processing. The base DN of the workflow
+ * node is the base DN of the related WorkflowImpl.
+ *
+ * How the workflow topology is built?
+ * A workflow node is a subordinate of another workflow node when
+ * the base DN of the former workflow is an ancestor of the base DN
+ * of the latter workflow.
+ *
+ * A subtree search on a workflow node is performed on the node itself as
+ * well as on all the subordinate nodes.
+ */
+public class WorkflowTopologyNode extends WorkflowTopology
+{
+  // Parent node of the current workflow node.
+  private WorkflowTopologyNode parent = null;
+
+
+  // The list of subordinate nodes of the current workflow node.
+  private ArrayList<WorkflowTopologyNode> subordinates =
+    new ArrayList<WorkflowTopologyNode>();
+
+
+  /**
+   * Creates a new node for a workflow topology. The new node is initialized
+   * with a WorkflowImpl which contains the real processing. Optionally,
+   * the node may have tasks to be executed before and/or after the real
+   * processing. In the current implementation, such pre and post workflow
+   * elements are not used.
+   *
+   * @param workflowImpl          the real processing attached to the node
+   * @param preWorkflowElements   the list of tasks to be executed before
+   *                              the real processing
+   * @param postWorkflowElements  the list of tasks to be executed after
+   *                              the real processing
+   */
+  public WorkflowTopologyNode(
+      WorkflowImpl workflowImpl,
+      WorkflowElement[] preWorkflowElements,
+      WorkflowElement[] postWorkflowElements
+      )
+  {
+    super(workflowImpl);
+  }
+
+
+  /**
+   * Executes an operation on a set of data being identified by the
+   * workflow node base DN.
+   *
+   * @param operation the operation to execute
+   */
+  public void execute(
+      Operation operation
+      )
+  {
+    // Execute the operation
+    getWorkflowImpl().execute(operation);
+
+    // For subtree search operation we need to go through the subordinate
+    // nodes.
+    if (operation.getOperationType() == OperationType.SEARCH)
+    {
+      executeSearchOnSubordinates((SearchOperation) operation);
+    }
+  }
+
+
+  /**
+   * Executes a search operation on the subordinate workflows.
+   *
+   * @param searchOp the search operation to execute
+   */
+  private void executeSearchOnSubordinates(
+      SearchOperation searchOp
+      )
+  {
+    // If the scope of the search is 'base' then it's useless to search
+    // in the subordinate workflows.
+    SearchScope originalScope = searchOp.getScope();
+    if (originalScope == SearchScope.BASE_OBJECT)
+    {
+      return;
+    }
+
+    // Elaborate the new search scope before executing the search operation
+    // in the subordinate workflows.
+    SearchScope newScope = elaborateScopeForSearchInSubordinates(originalScope);
+    searchOp.setScope(newScope);
+
+    // Let's search in the subordinate workflows.
+    WorkflowResultCode workflowResultCode = new WorkflowResultCode(
+        searchOp.getResultCode(), searchOp.getErrorMessage());
+    DN originalBaseDN = searchOp.getBaseDN();
+    for (WorkflowTopologyNode subordinate: getSubordinates())
+    {
+      // We have to change the operation request base DN to match the
+      // subordinate workflow base DN. Otherwise the workflow will
+      // return a no such entry result code as the operation request
+      // base DN is a superior of the subordinate workflow base DN.
+      DN subordinateDN = subordinate.getBaseDN();
+
+      // If the new search scope is 'base' and the search base DN does not
+      // map the subordinate workflow then skip the subordinate workflow.
+      if ((newScope == SearchScope.BASE_OBJECT)
+          && !subordinateDN.getParent().equals(originalBaseDN))
+      {
+        continue;
+      }
+
+      // If the request base DN is not a subordinate of the subordinate
+      // worklfow base DN then don't search in the subordinate workflow.
+      if (! originalBaseDN.isAncestorOf(subordinateDN))
+      {
+        continue;
+      }
+
+      // Set the new request base DN and do execute the
+      // operation in the subordinate workflow.
+      searchOp.setBaseDN(subordinateDN);
+      subordinate.execute(searchOp);
+      boolean sendReferenceEntry =
+        workflowResultCode.elaborateGlobalResultCode(
+          searchOp.getResultCode(), searchOp.getErrorMessage());
+      if (sendReferenceEntry)
+      {
+        // TODO jdemendi - turn a referral result code into a reference entry
+        // and send the reference entry to the client application
+      }
+    }
+
+    // Now we are done with the operation, let's restore the original
+    // base DN and search scope in the operation.
+    searchOp.setBaseDN(originalBaseDN);
+    searchOp.setScope(originalScope);
+
+    // Update the operation result code and error message
+    searchOp.setResultCode(workflowResultCode.resultCode());
+    searchOp.setErrorMessage(workflowResultCode.errorMessage());
+  }
+
+
+  /**
+   * Sets the parent workflow.
+   *
+   * @param parent  the parent workflow of the current workflow
+   */
+  public void setParent(WorkflowTopologyNode parent)
+  {
+    this.parent = parent;
+  }
+
+
+  /**
+   * Gets the parent workflow.
+   *
+   * @return the parent workflow.
+   */
+  public WorkflowTopologyNode getParent()
+  {
+    return parent;
+  }
+
+
+  /**
+   * Indicates whether the root workflow element is encapsulating a private
+   * local backend or not.
+   *
+   * @return <code>true</code> if the root workflow element encapsulates
+   *         a private local backend
+   */
+  public boolean isPrivate()
+  {
+    return getWorkflowImpl().isPrivate();
+  }
+
+
+  /**
+   * Gets the base DN of the workflow that handles a given dn. The elected
+   * workflow may be the current workflow or one of its subordiante workflows.
+   *
+   * @param  dn  the DN for which we are looking a parent DN
+   * @return the base DN which is the parent of the <code>dn</code>,
+   *         <code>null</code> if no parent DN was found
+   */
+  public DN getParentBaseDN(DN dn)
+  {
+    if (dn == null)
+    {
+      return null;
+    }
+
+    // parent base DN to return
+    DN parentBaseDN = null;
+
+    // Is the dn a subordinate of the current base DN?
+    DN curBaseDN = getBaseDN();
+    if (curBaseDN != null)
+    {
+      if (dn.isDescendantOf(curBaseDN))
+      {
+        // The dn may be handled by the current workflow.
+        // Now we have to check whether the dn is handled by
+        // a subordinate.
+        for (WorkflowTopologyNode subordinate: getSubordinates())
+        {
+          parentBaseDN = subordinate.getParentBaseDN(dn);
+          if (parentBaseDN != null)
+          {
+            // the dn is handled by a subordinate
+            break;
+          }
+        }
+
+        // If the dn is not handled by any subordinate, then it is
+        // handled by the current workflow.
+        if (parentBaseDN == null)
+        {
+          parentBaseDN = curBaseDN;
+        }
+      }
+    }
+
+    return parentBaseDN;
+  }
+
+
+  /**
+   * Adds a workflow to the list of workflow subordinates without
+   * additional control.
+   *
+   * @param newWorkflow     the workflow to add to the subordinate list
+   * @param parentWorkflow  the parent workflow of the new workflow
+   */
+  private void addSubordinateNoCheck(
+      WorkflowTopologyNode newWorkflow,
+      WorkflowTopologyNode parentWorkflow
+      )
+  {
+    subordinates.add(newWorkflow);
+    newWorkflow.setParent(parentWorkflow);
+  }
+
+
+  /**
+   * Adds a workflow to the subordinate list of the current workflow.
+   * Before we can add the new workflow, we have to check whether
+   * the new workflow is a parent workflow of any of the current
+   * subordinates (if so, then we have to add the subordinate in the
+   * subordinate list of the new workflow).
+   *
+   * @param newWorkflow  the workflow to add in the subordinate list
+   */
+  private void addSubordinate(
+      WorkflowTopologyNode newWorkflow
+      )
+  {
+    // Check whether subordinates of current workflow should move to the
+    // new workflow subordinate list.
+    ArrayList<WorkflowTopologyNode> curSubordinateList =
+        new ArrayList<WorkflowTopologyNode>(getSubordinates());
+
+    for (WorkflowTopologyNode curSubordinate: curSubordinateList)
+    {
+      DN newDN = newWorkflow.getBaseDN();
+      DN subordinateDN = curSubordinate.getBaseDN();
+      if (subordinateDN.isDescendantOf(newDN))
+      {
+        removeSubordinate(curSubordinate);
+        newWorkflow.addSubordinateNoCheck(curSubordinate, newWorkflow);
+      }
+    }
+
+    // add the new workflow in the current workflow subordinate list
+    addSubordinateNoCheck(newWorkflow, this);
+  }
+
+
+  /**
+   * Remove a workflow from the subordinate list.
+   *
+   * @param subordinate  the subordinate to remove from the subordinate list
+   */
+  public void removeSubordinate(
+      WorkflowTopologyNode subordinate
+      )
+  {
+    subordinates.remove(subordinate);
+  }
+
+
+  /**
+   * Tries to insert a new workflow in the subordinate list of one of the
+   * current workflow subordinate, or in the current workflow subordinate list.
+   *
+   * @param newWorkflow  the new workflow to insert
+   *
+   * @return <code>true</code> if the new workflow has been inserted
+   *         in any subordinate list
+   */
+  public boolean insertSubordinate(
+      WorkflowTopologyNode newWorkflow
+      )
+  {
+    // don't try to insert the workflow in itself!
+    if (newWorkflow == this)
+    {
+      return false;
+    }
+
+    // the returned status
+    boolean insertDone = false;
+
+    DN parentBaseDN = getBaseDN();
+    DN newBaseDN    = newWorkflow.getBaseDN();
+
+    // dont' try to insert workflows when baseDNs are the same on both
+    // workflows
+    if (parentBaseDN.equals(newBaseDN))
+    {
+      return false;
+    }
+
+    // try to insert the new workflow
+    if (newBaseDN.isDescendantOf(parentBaseDN))
+    {
+      // the new workflow is a subordinate for this parent DN, let's
+      // insert the new workflow in the list of subordinates
+      for (WorkflowTopologyNode subordinate: getSubordinates())
+      {
+        insertDone = subordinate.insertSubordinate(newWorkflow);
+        if (insertDone)
+        {
+          // the newBaseDN is handled by a subordinate
+          break;
+        }
+      }
+
+      // if the newBaseDN is not handled by a subordinate then the workflow
+      // is inserted it in the current workflow subordinate list
+      if (! insertDone)
+      {
+        addSubordinate(newWorkflow);
+        insertDone = true;
+      }
+    }
+
+    return insertDone;
+  }
+
+
+  /**
+   * Removes the current workflow from the parent subordinate list
+   * and attach the workflow subordinates to the parent workflow.
+   *
+   * Example: the workflow to remove is w2
+   *
+   *        w1             w1
+   *        |             / \
+   *        w2     ==>   w3  w4
+   *       / \
+   *     w3   w4
+   *
+   * - Subordinate list of w1 is updated with w3 and w4.
+   * - Parent workflow of w3 and w4 is now w1.
+   */
+  public void remove()
+  {
+    // First of all, remove the workflow from the parent subordinate list
+    WorkflowTopologyNode parent = getParent();
+    if (parent != null)
+    {
+      parent.removeSubordinate(this);
+    }
+
+    // Then set the parent of each subordinate and attach the subordinate to
+    // the parent.
+    for (WorkflowTopologyNode subordinate: getSubordinates())
+    {
+      subordinate.setParent(parent);
+      if (parent != null)
+      {
+        parent.addSubordinateNoCheck(subordinate, parent);
+      }
+    }
+  }
+
+
+  /**
+   * Gets the list of workflow subordinates.
+   *
+   * @return the list of workflow subordinates
+   */
+  public ArrayList<WorkflowTopologyNode> getSubordinates()
+  {
+    return subordinates;
+  }
+
+
+  /**
+   * Gets the highest workflow in the topology that can handle the requestDN.
+   * The highest workflow is either the current workflow or one of its
+   * subordinates.
+   *
+   * @param requestDN  The DN for which we search for a workflow
+   * @return the highest workflow that can handle the requestDN
+   *         <code>null</code> if none was found
+   */
+  public WorkflowTopologyNode getWorkflowCandidate(
+      DN requestDN
+      )
+  {
+    // the returned workflow
+    WorkflowTopologyNode workflowCandidate = null;
+
+    // does the current workflow handle the request baseDN?
+    DN baseDN = getParentBaseDN(requestDN);
+    if (baseDN == null)
+    {
+      // the current workflow does not handle the requestDN,
+      // let's return null
+    }
+    else
+    {
+      // is there any subordinate that can handle the requestDN?
+      for (WorkflowTopologyNode subordinate: getSubordinates())
+      {
+        workflowCandidate = subordinate.getWorkflowCandidate(requestDN);
+        if (workflowCandidate != null)
+        {
+          break;
+        }
+      }
+
+      // none of the subordinates can handle the requestDN, so the current
+      // workflow is the best root workflow candidate
+      if (workflowCandidate == null)
+      {
+        workflowCandidate = this;
+      }
+    }
+
+    return workflowCandidate;
+  }
+
+
+  /**
+   * Dumps info from the current workflow for debug purpose.
+   *
+   * @param leftMargin  white spaces used to indent the traces
+   * @return a string buffer that contains trace information
+   */
+  public StringBuffer toString(String leftMargin)
+  {
+    StringBuffer sb = new StringBuffer();
+
+    // display the baseDN
+    DN baseDN = getBaseDN();
+    sb.append(leftMargin + "Workflow baseDN:[");
+    if (baseDN.isNullDN())
+    {
+      sb.append(" \"\"");
+    }
+    else
+    {
+      sb.append(" \"" + baseDN.toString() + "\"");
+    }
+    sb.append(" ]\n");
+
+    // display parent workflow
+    sb.append(leftMargin + "Parent: " + getParent() + "\n");
+
+    // dump each subordinate
+    sb.append(leftMargin + "List of subordinates:\n");
+    ArrayList<WorkflowTopologyNode> subordinates = getSubordinates();
+    if (subordinates.isEmpty())
+    {
+      sb.append(leftMargin + "   NONE\n");
+    }
+    else
+    {
+      for (WorkflowTopologyNode subordinate: getSubordinates())
+      {
+        sb.append(subordinate.toString(leftMargin + "   "));
+      }
+    }
+
+    return sb;
+  }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/StaticGroup.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/StaticGroup.java
index 5e19363..95e2b05 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/StaticGroup.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/StaticGroup.java
@@ -35,7 +35,7 @@
 
 import org.opends.server.admin.std.server.GroupImplementationCfg;
 import org.opends.server.api.Group;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.config.ConfigException;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.Attribute;
@@ -460,8 +460,8 @@
 
       InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
-      ModifyOperation modifyOperation =
-           new ModifyOperation(conn, conn.nextOperationID(),
+      ModifyOperationBasis modifyOperation =
+           new ModifyOperationBasis(conn, conn.nextOperationID(),
                                conn.nextMessageID(), requestControls,
                                groupEntryDN, mods);
       modifyOperation.run();
@@ -524,8 +524,8 @@
 
       InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
-      ModifyOperation modifyOperation =
-           new ModifyOperation(conn, conn.nextOperationID(),
+      ModifyOperationBasis modifyOperation =
+           new ModifyOperationBasis(conn, conn.nextOperationID(),
                                conn.nextMessageID(), requestControls,
                                groupEntryDN, mods);
       modifyOperation.run();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java
index 8953361..1a87228 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java
@@ -61,6 +61,7 @@
 import static org.opends.server.messages.ConfigMessages.*;
 import static org.opends.server.messages.CoreMessages.*;
 import static org.opends.server.messages.MessageHandler.*;
+import org.opends.server.types.AbstractOperation;
 
 
 
@@ -122,7 +123,7 @@
   private int numWorkerThreads;
 
   // The queue that will be used to actually hold the pending operations.
-  private LinkedBlockingQueue<Operation> opQueue;
+  private LinkedBlockingQueue<AbstractOperation> opQueue;
 
   // The lock used to provide threadsafe access for the queue.
   private ReentrantLock queueLock;
@@ -167,11 +168,11 @@
     // Create the actual work queue.
     if (maxCapacity > 0)
     {
-      opQueue = new LinkedBlockingQueue<Operation>(maxCapacity);
+      opQueue = new LinkedBlockingQueue<AbstractOperation>(maxCapacity);
     }
     else
     {
-      opQueue = new LinkedBlockingQueue<Operation>();
+      opQueue = new LinkedBlockingQueue<AbstractOperation>();
     }
 
 
@@ -295,8 +296,7 @@
    *                              down or the pending operation queue is already
    *                              at its maximum capacity).
    */
-  @Override()
-  public void submitOperation(Operation operation)
+  public void submitOperation(AbstractOperation operation)
          throws DirectoryException
   {
     if (shutdownRequested)
@@ -331,7 +331,7 @@
    *          if the server is shutting down and no more operations will be
    *          processed.
    */
-  public Operation nextOperation(TraditionalWorkerThread workerThread)
+  public AbstractOperation nextOperation(TraditionalWorkerThread workerThread)
   {
     return retryNextOperation(workerThread, 0);
   }
@@ -354,7 +354,8 @@
    *          if the server is shutting down and no more operations will be
    *          processed, or if there have been too many consecutive failures.
    */
-  private Operation retryNextOperation(TraditionalWorkerThread workerThread,
+  private AbstractOperation retryNextOperation(
+                                       TraditionalWorkerThread workerThread,
                                        int numFailures)
   {
     // See if we should kill off this thread.  This could be necessary if the
@@ -414,7 +415,7 @@
     {
       while (true)
       {
-        Operation nextOperation = opQueue.poll(5, TimeUnit.SECONDS);
+        AbstractOperation nextOperation = opQueue.poll(5, TimeUnit.SECONDS);
         if (nextOperation == null)
         {
           // There was no work to do in the specified length of time.  See if
@@ -512,7 +513,7 @@
    * @return  <CODE>true</CODE> if the provided request was present in the queue
    *          and was removed successfully, or <CODE>false</CODE> it not.
    */
-  public boolean removeOperation(Operation operation)
+  public boolean removeOperation(AbstractOperation operation)
   {
     return opQueue.remove(operation);
   }
@@ -642,20 +643,22 @@
 
       try
       {
-        LinkedBlockingQueue<Operation> newOpQueue;
+        LinkedBlockingQueue<AbstractOperation> newOpQueue;
         if (newMaxCapacity > 0)
         {
-          newOpQueue = new LinkedBlockingQueue<Operation>(newMaxCapacity);
+          newOpQueue =
+            new LinkedBlockingQueue<AbstractOperation>(newMaxCapacity);
         }
         else
         {
-          newOpQueue = new LinkedBlockingQueue<Operation>();
+          newOpQueue = new LinkedBlockingQueue<AbstractOperation>();
         }
 
-        LinkedBlockingQueue<Operation> oldOpQueue = opQueue;
+        LinkedBlockingQueue<AbstractOperation> oldOpQueue = opQueue;
         opQueue = newOpQueue;
 
-        LinkedList<Operation> pendingOps = new LinkedList<Operation>();
+        LinkedList<AbstractOperation> pendingOps =
+          new LinkedList<AbstractOperation>();
         oldOpQueue.drainTo(pendingOps);
 
 
@@ -665,10 +668,10 @@
         // loop a few times to get everything in there.
         while (! pendingOps.isEmpty())
         {
-          Iterator<Operation> iterator = pendingOps.iterator();
+          Iterator<AbstractOperation> iterator = pendingOps.iterator();
           while (iterator.hasNext())
           {
-            Operation o = iterator.next();
+            AbstractOperation o = iterator.next();
             try
             {
               if (newOpQueue.offer(o, 1000, TimeUnit.MILLISECONDS))
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkerThread.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkerThread.java
index 4978266..5cd4053 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkerThread.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/TraditionalWorkerThread.java
@@ -32,13 +32,13 @@
 
 import org.opends.server.api.DirectoryThread;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.CancelRequest;
 import org.opends.server.types.CancelResult;
 import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
-import org.opends.server.types.Operation;
 
 import static org.opends.server.loggers.ErrorLogger.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
@@ -72,7 +72,7 @@
   private boolean waitingForWork;
 
   // The operation that this worker thread is currently processing.
-  private Operation operation;
+  private AbstractOperation operation;
 
   // The handle to the actual thread for this worker thread.
   private Thread workerThread;
@@ -221,8 +221,9 @@
                                       String.valueOf(operation),
                                       stackTraceToSingleLineString(e));
 
-          operation.disconnectClient(DisconnectReason.SERVER_ERROR, true,
-                                     message, msgID);
+          operation.disconnectClient(
+                                     DisconnectReason.SERVER_ERROR,
+                                     true, message, msgID);
         }
         catch (Exception e2)
         {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java b/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
index aa85bab..9cb840f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
@@ -443,28 +443,26 @@
 
     if(code == SUCCESS)
     {
-      Entry entry = addOperation.getEntryToAdd();
-
       StringBuilder buffer = new StringBuilder(50);
       buffer.append("# ");
       buffer.append(TimeThread.getLocalTime());
       buffer.append(EOL);
 
       buffer.append("dn:");
-      encodeValue(entry.getDN().toString(), buffer);
+      encodeValue(addOperation.getEntryDN().toString(), buffer);
       buffer.append(EOL);
 
       buffer.append("changetype: add");
       buffer.append(EOL);
 
-      for (String ocName : entry.getObjectClasses().values())
+      for (String ocName : addOperation.getObjectClasses().values())
       {
         buffer.append("objectClass: ");
         buffer.append(ocName);
         buffer.append(EOL);
       }
 
-      for (List<Attribute> attrList : entry.getUserAttributes().values())
+      for (List<Attribute> attrList : addOperation.getUserAttributes().values())
       {
         for (Attribute a : attrList)
         {
@@ -478,7 +476,8 @@
         }
       }
 
-      for (List<Attribute> attrList : entry.getOperationalAttributes().values())
+      for (List<Attribute> attrList :
+        addOperation.getOperationalAttributes().values())
       {
         for (Attribute a : attrList)
         {
@@ -563,15 +562,13 @@
 
     if(code == SUCCESS)
     {
-      Entry entry = deleteOperation.getEntryToDelete();
-
       StringBuilder buffer = new StringBuilder(50);
       buffer.append("# ");
       buffer.append(TimeThread.getLocalTime());
       buffer.append(EOL);
 
       buffer.append("dn:");
-      encodeValue(entry.getDN().toString(), buffer);
+      encodeValue(deleteOperation.getEntryDN().toString(), buffer);
       buffer.append(EOL);
 
       buffer.append("changetype: delete");
@@ -629,15 +626,13 @@
 
     if(code == SUCCESS)
     {
-      Entry entry = modifyOperation.getModifiedEntry();
-
       StringBuilder buffer = new StringBuilder(50);
       buffer.append("# ");
       buffer.append(TimeThread.getLocalTime());
       buffer.append(EOL);
 
       buffer.append("dn:");
-      encodeValue(entry.getDN().toString(), buffer);
+      encodeValue(modifyOperation.getEntryDN().toString(), buffer);
       buffer.append(EOL);
 
       buffer.append("changetype: modify");
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
index 276fe40..b6e7111 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6169,6 +6169,16 @@
        CATEGORY_MASK_CORE | SEVERITY_MASK_NOTICE | 619;
 
 
+  /**
+   * The message ID for the message that will be used if a workflow is
+   * configured with no root workflow element. No root workflow element
+   * means no processing on the DIT attached to the workflow, which makes
+   * no sense.
+   */
+  public static final int MSGID_WARNING_ROOT_WORKFLOW_ELEMENT_NOT_DEFINED =
+    CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_WARNING | 620;
+
+
 
   /**
    * The message ID for the message that will be used if an attribute used an
@@ -8443,6 +8453,11 @@
                     "The Directory Server is leaving lockdown mode and will " +
                     "resume normal operation");
 
+    registerMessage(MSGID_WARNING_ROOT_WORKFLOW_ELEMENT_NOT_DEFINED,
+        "The workflow with base DN \"%s\" has no root workflow " +
+        "element"
+        );
+
 
     registerMessage(MSGID_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN,
                     "Unable to decode the provided attribute because it " +
@@ -8458,6 +8473,10 @@
     registerMessage(MSGID_ENTRYENCODECFG_INVALID_LENGTH,
                     "Unable to decode the provided entry encode " +
                     "configuration element because it has an invalid length");
+
+    registerMessage(MSGID_WARNING_ROOT_WORKFLOW_ELEMENT_NOT_DEFINED,
+                    "The workflow with base DN \"%s\" has no root workflow " +
+                    "element");
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
index 68f1295..64e3113 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
@@ -28,6 +28,13 @@
 
 
 
+import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.messages.ProtocolMessages.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -43,18 +50,11 @@
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.ConnectionHandler;
 import org.opends.server.api.ConnectionSecurityProvider;
-import org.opends.server.core.AddOperation;
-import org.opends.server.core.BindOperation;
-import org.opends.server.core.CompareOperation;
-import org.opends.server.core.DeleteOperation;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ExtendedOperation;
-import org.opends.server.core.ModifyOperation;
-import org.opends.server.core.ModifyDNOperation;
-import org.opends.server.core.SearchOperation;
-import org.opends.server.extensions.
-            InternalConnectionSecurityProvider;
+import org.opends.server.core.*;
+import org.opends.server.extensions.*;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AuthenticationInfo;
@@ -62,37 +62,29 @@
 import org.opends.server.types.CancelRequest;
 import org.opends.server.types.CancelResult;
 import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
 import org.opends.server.types.DisconnectReason;
+import org.opends.server.types.Entry;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DN;
-import org.opends.server.types.Entry;
 import org.opends.server.types.IntermediateResponse;
-import org.opends.server.types.Modification;
 import org.opends.server.types.LDAPException;
+import org.opends.server.types.Modification;
 import org.opends.server.types.ObjectClass;
 import org.opends.server.types.Operation;
+import org.opends.server.types.RDN;
 import org.opends.server.types.RawAttribute;
 import org.opends.server.types.RawFilter;
 import org.opends.server.types.RawModification;
 import org.opends.server.types.ResultCode;
-import org.opends.server.types.RDN;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchResultReference;
 import org.opends.server.types.SearchScope;
 
-import static org.opends.server.config.ConfigConstants.*;
-import org.opends.server.types.DebugLogLevel;
-import static org.opends.server.loggers.ErrorLogger.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
-import static org.opends.server.messages.ProtocolMessages.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
 
 
 /**
@@ -136,7 +128,7 @@
   private AuthenticationInfo authenticationInfo;
 
   // The empty operation list for this connection.
-  private LinkedList<Operation> operationList;
+  private LinkedList<AbstractOperation> operationList;
 
   // The connection ID for this client connection.
   private long connectionID;
@@ -237,7 +229,7 @@
     }
 
     connectionID  = nextConnectionID.getAndDecrement();
-    operationList = new LinkedList<Operation>();
+    operationList = new LinkedList<AbstractOperation>();
 
     try
     {
@@ -274,7 +266,7 @@
     super.setLookthroughLimit(0);
 
     connectionID  = nextConnectionID.getAndDecrement();
-    operationList = new LinkedList<Operation>();
+    operationList = new LinkedList<AbstractOperation>();
 
     try
     {
@@ -693,8 +685,9 @@
   public AddOperation processAdd(ByteString rawEntryDN,
                                  List<RawAttribute> rawAttributes)
   {
-    AddOperation addOperation =
-         new AddOperation(this, nextOperationID(), nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(this, nextOperationID(),
+                          nextMessageID(),
                           new ArrayList<Control>(0), rawEntryDN,
                           rawAttributes);
     addOperation.setInternalOperation(true);
@@ -728,8 +721,9 @@
                            Map<AttributeType,List<Attribute>>
                                 operationalAttributes)
   {
-    AddOperation addOperation =
-         new AddOperation(this, nextOperationID(), nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(this, nextOperationID(),
+                          nextMessageID(),
                           new ArrayList<Control>(0), entryDN,
                           objectClasses, userAttributes,
                           operationalAttributes);
@@ -795,8 +789,9 @@
   public BindOperation processSimpleBind(ByteString rawBindDN,
                                          ByteString password)
   {
-    BindOperation bindOperation =
-         new BindOperation(this, nextOperationID(), nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(this, nextOperationID(),
+                           nextMessageID(),
                            new ArrayList<Control>(0),
                            PROTOCOL_VERSION, rawBindDN, password);
     bindOperation.setInternalOperation(true);
@@ -822,8 +817,9 @@
   public BindOperation processSimpleBind(DN bindDN,
                                          ByteString password)
   {
-    BindOperation bindOperation =
-         new BindOperation(this, nextOperationID(), nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(this, nextOperationID(),
+                           nextMessageID(),
                            new ArrayList<Control>(0),
                            PROTOCOL_VERSION, bindDN, password);
     bindOperation.setInternalOperation(true);
@@ -851,8 +847,9 @@
                             String saslMechanism,
                             ASN1OctetString saslCredentials)
   {
-    BindOperation bindOperation =
-         new BindOperation(this, nextOperationID(), nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(this, nextOperationID(),
+                           nextMessageID(),
                            new ArrayList<Control>(0),
                            PROTOCOL_VERSION, rawBindDN, saslMechanism,
                            saslCredentials);
@@ -881,8 +878,9 @@
                             String saslMechanism,
                             ASN1OctetString saslCredentials)
   {
-    BindOperation bindOperation =
-         new BindOperation(this, nextOperationID(), nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(this, nextOperationID(),
+                           nextMessageID(),
                            new ArrayList<Control>(0),
                            PROTOCOL_VERSION, bindDN, saslMechanism,
                            saslCredentials);
@@ -1010,8 +1008,9 @@
    */
   public DeleteOperation processDelete(ByteString rawEntryDN)
   {
-    DeleteOperation deleteOperation =
-         new DeleteOperation(this, nextOperationID(), nextMessageID(),
+    DeleteOperationBasis deleteOperation =
+         new DeleteOperationBasis(this, nextOperationID(),
+                             nextMessageID(),
                              new ArrayList<Control>(0), rawEntryDN);
     deleteOperation.setInternalOperation(true);
 
@@ -1033,8 +1032,9 @@
    */
   public DeleteOperation processDelete(DN entryDN)
   {
-    DeleteOperation deleteOperation =
-         new DeleteOperation(this, nextOperationID(), nextMessageID(),
+    DeleteOperationBasis deleteOperation =
+         new DeleteOperationBasis(this, nextOperationID(),
+                             nextMessageID(),
                              new ArrayList<Control>(0), entryDN);
     deleteOperation.setInternalOperation(true);
 
@@ -1066,7 +1066,6 @@
                                new ArrayList<Control>(0), requestOID,
                                requestValue);
     extendedOperation.setInternalOperation(true);
-
     extendedOperation.run();
     return extendedOperation;
   }
@@ -1111,13 +1110,14 @@
   public ModifyOperation processModify(ByteString rawEntryDN,
                               List<RawModification> rawModifications)
   {
-    ModifyOperation modifyOperation =
-         new ModifyOperation(this, nextOperationID(), nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(this, nextOperationID(),
+                             nextMessageID(),
                              new ArrayList<Control>(0), rawEntryDN,
                              rawModifications);
     modifyOperation.setInternalOperation(true);
-
     modifyOperation.run();
+
     return modifyOperation;
   }
 
@@ -1138,13 +1138,14 @@
   public ModifyOperation processModify(DN entryDN,
                               List<Modification> modifications)
   {
-    ModifyOperation modifyOperation =
-         new ModifyOperation(this, nextOperationID(), nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(this, nextOperationID(),
+                             nextMessageID(),
                              new ArrayList<Control>(0), entryDN,
                              modifications);
     modifyOperation.setInternalOperation(true);
-
     modifyOperation.run();
+
     return modifyOperation;
   }
 
@@ -1832,7 +1833,7 @@
    * @return  The set of operations in progress for this client
    *          connection.
    */
-  public Collection<Operation> getOperationsInProgress()
+  public Collection<AbstractOperation> getOperationsInProgress()
   {
     return operationList;
   }
@@ -1849,7 +1850,7 @@
    *          or <CODE>null</CODE> if no such operation could be
    *          found.
    */
-  public Operation getOperationInProgress(int messageID)
+  public AbstractOperation getOperationInProgress(int messageID)
   {
     // Internal operations will not be tracked.
     return null;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java
index 3e1a2a1..91010af 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java
@@ -33,7 +33,7 @@
 import java.util.List;
 
 import org.opends.server.api.ClientConnection;
-import org.opends.server.core.SearchOperation;
+import org.opends.server.core.SearchOperationBasis;
 import org.opends.server.types.ByteString;
 import org.opends.server.types.Control;
 import org.opends.server.types.DN;
@@ -55,7 +55,7 @@
  * client since there is no real client.
  */
 public class InternalSearchOperation
-       extends SearchOperation
+       extends SearchOperationBasis
 {
   // The internal search listener for this search, if one was
   // provided.
@@ -284,5 +284,26 @@
                                                    searchReference);
     }
   }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void sendSearchEntry(SearchResultEntry searchEntry)
+    throws DirectoryException
+    {
+    addSearchEntry(searchEntry);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean sendSearchReference(SearchResultReference
+      searchReference)
+  throws DirectoryException
+  {
+    addSearchReference(searchReference);
+    return true;
+  }
+
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
index bd282b3..67c2fd6 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java
@@ -43,6 +43,7 @@
 import org.opends.server.protocols.internal.InternalSearchOperation ;
 import org.opends.server.protocols.internal.InternalSearchListener;
 
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.CancelRequest;
@@ -88,7 +89,7 @@
   private ConnectionSecurityProvider securityProvider;
 
   // The empty operation list for this connection.
-  private LinkedList<Operation> operationList;
+  private LinkedList<AbstractOperation> operationList;
 
   // The connection ID for this client connection.
   private long connectionID;
@@ -142,7 +143,7 @@
           true,
           MSGID_LDAP_CONNHANDLER_REJECTED_BY_SERVER);
     }
-    operationList = new LinkedList<Operation>();
+    operationList = new LinkedList<AbstractOperation>();
 
     try
     {
@@ -468,11 +469,11 @@
    * @return  A reference to the add operation that was processed and contains
    *          information about the result of the processing.
    */
-  public AddOperation processAdd(ASN1OctetString rawEntryDN,
+  public AddOperationBasis processAdd(ASN1OctetString rawEntryDN,
                                  ArrayList<RawAttribute> rawAttributes)
   {
-    AddOperation addOperation =
-         new AddOperation(this, nextOperationID(), nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(this, nextOperationID(), nextMessageID(),
                           new ArrayList<Control>(0), rawEntryDN, rawAttributes);
 
     addOperation.run();
@@ -514,10 +515,10 @@
    * @return  A reference to the delete operation that was processed and
    *          contains information about the result of the processing.
    */
-  public DeleteOperation processDelete(ASN1OctetString rawEntryDN)
+  public DeleteOperationBasis processDelete(ASN1OctetString rawEntryDN)
   {
-    DeleteOperation deleteOperation =
-         new DeleteOperation(this, nextOperationID(), nextMessageID(),
+    DeleteOperationBasis deleteOperation =
+         new DeleteOperationBasis(this, nextOperationID(), nextMessageID(),
                              new ArrayList<Control>(0), rawEntryDN);
 
     deleteOperation.run();
@@ -560,11 +561,11 @@
    * @return  A reference to the modify operation that was processed and
    *          contains information about the result of the processing
    */
-  public ModifyOperation processModify(ASN1OctetString rawEntryDN,
+  public ModifyOperationBasis processModify(ASN1OctetString rawEntryDN,
                               ArrayList<RawModification> rawModifications)
   {
-    ModifyOperation modifyOperation =
-         new ModifyOperation(this, nextOperationID(), nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(this, nextOperationID(), nextMessageID(),
                              new ArrayList<Control>(0), rawEntryDN,
                              rawModifications);
 
@@ -714,8 +715,6 @@
                                      typesOnly, filter, attributes,
                                      searchListener);
 
-
-
     searchOperation.run();
     return searchOperation;
   }
@@ -826,6 +825,7 @@
     {
       UnbindOperation unbindOp = new UnbindOperation((ClientConnection) this,
           this.nextOperationID(), this.nextMessageID(), null);
+
       unbindOp.run();
     }
     catch (Exception e)
@@ -894,7 +894,7 @@
    *
    * @return  The set of operations in progress for this client connection.
    */
-  public Collection<Operation> getOperationsInProgress()
+  public Collection<AbstractOperation> getOperationsInProgress()
   {
     return operationList;
   }
@@ -909,7 +909,7 @@
    * @return  The operation in progress with the specified message ID, or
    *          <CODE>null</CODE> if no such operation could be found.
    */
-  public Operation getOperationInProgress(int messageID)
+  public AbstractOperation getOperationInProgress(int messageID)
   {
     // Jmx operations will not be tracked.
     return null;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java
index 70e6069..47bb286 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java
@@ -32,7 +32,7 @@
 import javax.security.auth.Subject;
 
 import org.opends.server.api.plugin.PostConnectPluginResult;
-import org.opends.server.core.BindOperation;
+import org.opends.server.core.BindOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.PluginConfigManager;
 import org.opends.server.messages.CoreMessages;
@@ -261,7 +261,7 @@
     JmxClientConnection jmxClientConnection = new JmxClientConnection(
         jmxConnectionHandler, authInfo);
 
-    BindOperation bindOp = new BindOperation(jmxClientConnection,
+    BindOperationBasis bindOp = new BindOperationBasis(jmxClientConnection,
         jmxClientConnection.nextOperationID(),
         jmxClientConnection.nextMessageID(), requestControls,
         jmxConnectionHandler.getRMIConnector().getProtocolVersion(),
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
index 802cb00..7b052bd 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
@@ -28,6 +28,17 @@
 
 
 
+import static org.opends.server.loggers.AccessLogger.logDisconnect;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.messages.ProtocolMessages.*;
+import static org.opends.server.protocols.ldap.LDAPConstants.*;
+import static org.opends.server.util.StaticUtils.getBacktrace;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.SocketChannel;
@@ -43,30 +54,34 @@
 import org.opends.server.api.ConnectionHandler;
 import org.opends.server.api.ConnectionSecurityProvider;
 import org.opends.server.core.AbandonOperation;
-import org.opends.server.core.AddOperation;
-import org.opends.server.core.BindOperation;
+import org.opends.server.core.AddOperationBasis;
+import org.opends.server.core.BindOperationBasis;
 import org.opends.server.core.CompareOperation;
-import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ExtendedOperation;
-import org.opends.server.core.ModifyOperation;
 import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.core.PersistentSearch;
 import org.opends.server.core.PluginConfigManager;
 import org.opends.server.core.SearchOperation;
+import org.opends.server.core.SearchOperationBasis;
 import org.opends.server.core.UnbindOperation;
 import org.opends.server.extensions.NullConnectionSecurityProvider;
 import org.opends.server.extensions.TLSCapableConnection;
 import org.opends.server.extensions.TLSConnectionSecurityProvider;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.protocols.asn1.ASN1Element;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.asn1.ASN1Sequence;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.CancelRequest;
 import org.opends.server.types.CancelResult;
 import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.DisconnectReason;
-import org.opends.server.types.DN;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.IntermediateResponse;
@@ -75,16 +90,6 @@
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchResultReference;
 
-import static org.opends.server.loggers.AccessLogger.*;
-import org.opends.server.types.DebugLogLevel;
-import static org.opends.server.loggers.ErrorLogger.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
-import static org.opends.server.messages.MessageHandler.*;
-import static org.opends.server.messages.ProtocolMessages.*;
-import static org.opends.server.protocols.ldap.LDAPConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
 
 
 /**
@@ -127,7 +132,7 @@
   private byte[] elementValue;
 
   // The set of all operations currently in progress on this connection.
-  private ConcurrentHashMap<Integer,Operation> operationsInProgress;
+  private ConcurrentHashMap<Integer,AbstractOperation> operationsInProgress;
 
   // The connection security provider that was in use for the client connection
   // before switching to a TLS-based provider.
@@ -239,7 +244,7 @@
     nextOperationID      = new AtomicLong(0);
     connectionValid      = true;
     disconnectRequested  = false;
-    operationsInProgress = new ConcurrentHashMap<Integer,Operation>();
+    operationsInProgress = new ConcurrentHashMap<Integer,AbstractOperation>();
     keepStats            = connectionHandler.keepStats();
     protocol             = "LDAP";
 
@@ -624,7 +629,7 @@
         break;
       case BIND:
         ASN1OctetString serverSASLCredentials =
-             ((BindOperation) operation).getServerSASLCredentials();
+             ((BindOperationBasis) operation).getServerSASLCredentials();
         protocolOp = new BindResponseProtocolOp(resultCode.getIntValue(),
                               errorMessage.toString(), matchedDN,
                               referralURLs, serverSASLCredentials);
@@ -1117,7 +1122,7 @@
    *
    * @return  The set of operations in progress for this client connection.
    */
-  public Collection<Operation> getOperationsInProgress()
+  public Collection<AbstractOperation> getOperationsInProgress()
   {
     return operationsInProgress.values();
   }
@@ -1132,7 +1137,7 @@
    * @return  The operation in progress with the specified message ID, or
    *          <CODE>null</CODE> if no such operation could be found.
    */
-  public Operation getOperationInProgress(int messageID)
+  public AbstractOperation getOperationInProgress(int messageID)
   {
     return operationsInProgress.get(messageID);
   }
@@ -1150,7 +1155,7 @@
    *                              (e.g., the client already has reached the
    *                              maximum allowed concurrent requests).
    */
-  public void addOperationInProgress(Operation operation)
+  public void addOperationInProgress(AbstractOperation operation)
          throws DirectoryException
   {
     int messageID = operation.getMessageID();
@@ -1174,7 +1179,7 @@
 
       // See if there is already an operation in progress with the same message
       // ID.  If so, then we can't allow it.
-      Operation op = operationsInProgress.get(messageID);
+      AbstractOperation op = operationsInProgress.get(messageID);
       if (op != null)
       {
         int    msgID   = MSGID_LDAP_CLIENT_DUPLICATE_MESSAGE_ID;
@@ -1235,7 +1240,7 @@
    */
   public boolean removeOperationInProgress(int messageID)
   {
-    Operation operation = operationsInProgress.remove(messageID);
+    AbstractOperation operation = operationsInProgress.remove(messageID);
     return (operation != null);
   }
 
@@ -1254,7 +1259,7 @@
   public CancelResult cancelOperation(int messageID,
                                       CancelRequest cancelRequest)
   {
-    Operation op = operationsInProgress.get(messageID);
+    AbstractOperation op = operationsInProgress.get(messageID);
     if (op == null)
     {
       // See if the operation is in the list of persistent searches.
@@ -1303,7 +1308,7 @@
 
     try
     {
-      for (Operation o : operationsInProgress.values())
+      for (AbstractOperation o : operationsInProgress.values())
       {
         try
         {
@@ -1369,7 +1374,7 @@
           continue;
         }
 
-        Operation o = operationsInProgress.get(msgID);
+        AbstractOperation o = operationsInProgress.get(msgID);
         if (o != null)
         {
           try
@@ -1854,8 +1859,8 @@
   {
     // Create the add operation and add it into the work queue.
     AddRequestProtocolOp protocolOp = message.getAddRequestProtocolOp();
-    AddOperation addOp =
-         new AddOperation(this, nextOperationID.getAndIncrement(),
+    AddOperationBasis addOp =
+         new AddOperationBasis(this, nextOperationID.getAndIncrement(),
                           message.getMessageID(), controls, protocolOp.getDN(),
                           protocolOp.getAttributes());
 
@@ -1933,17 +1938,17 @@
 
     ASN1OctetString bindDN = protocolOp.getDN();
 
-    BindOperation bindOp;
+    BindOperationBasis bindOp;
     switch (protocolOp.getAuthenticationType())
     {
       case SIMPLE:
-        bindOp = new BindOperation(this, nextOperationID.getAndIncrement(),
+        bindOp = new BindOperationBasis(this, nextOperationID.getAndIncrement(),
                                    message.getMessageID(), controls,
                                    versionString, bindDN,
                                    protocolOp.getSimplePassword());
         break;
       case SASL:
-        bindOp = new BindOperation(this, nextOperationID.getAndIncrement(),
+        bindOp = new BindOperationBasis(this, nextOperationID.getAndIncrement(),
                                    message.getMessageID(), controls,
                                    versionString, bindDN,
                                    protocolOp.getSASLMechanism(),
@@ -1960,7 +1965,6 @@
         return false;
     }
 
-
     // Add the operation into the work queue.
     try
     {
@@ -2019,7 +2023,6 @@
                               protocolOp.getDN(), protocolOp.getAttributeType(),
                               protocolOp.getAssertionValue());
 
-
     // Add the operation into the work queue.
     try
     {
@@ -2064,12 +2067,11 @@
                                        ArrayList<Control> controls)
   {
     DeleteRequestProtocolOp protocolOp = message.getDeleteRequestProtocolOp();
-    DeleteOperation deleteOp =
-         new DeleteOperation(this, nextOperationID.getAndIncrement(),
+    DeleteOperationBasis deleteOp =
+         new DeleteOperationBasis(this, nextOperationID.getAndIncrement(),
                              message.getMessageID(), controls,
                              protocolOp.getDN());
 
-
     // Add the operation into the work queue.
     try
     {
@@ -2139,7 +2141,6 @@
                                message.getMessageID(), controls,
                                protocolOp.getOID(), protocolOp.getValue());
 
-
     // Add the operation into the work queue.
     try
     {
@@ -2184,12 +2185,11 @@
                                        ArrayList<Control> controls)
   {
     ModifyRequestProtocolOp protocolOp = message.getModifyRequestProtocolOp();
-    ModifyOperation modifyOp =
-         new ModifyOperation(this, nextOperationID.getAndIncrement(),
+    ModifyOperationBasis modifyOp =
+         new ModifyOperationBasis(this, nextOperationID.getAndIncrement(),
                              message.getMessageID(), controls,
                              protocolOp.getDN(), protocolOp.getModifications());
 
-
     // Add the operation into the work queue.
     try
     {
@@ -2241,7 +2241,6 @@
                                protocolOp.deleteOldRDN(),
                                protocolOp.getNewSuperior());
 
-
     // Add the operation into the work queue.
     try
     {
@@ -2286,8 +2285,8 @@
                                        ArrayList<Control> controls)
   {
     SearchRequestProtocolOp protocolOp = message.getSearchRequestProtocolOp();
-    SearchOperation searchOp =
-         new SearchOperation(this, nextOperationID.getAndIncrement(),
+    SearchOperationBasis searchOp =
+         new SearchOperationBasis(this, nextOperationID.getAndIncrement(),
                              message.getMessageID(), controls,
                              protocolOp.getBaseDN(), protocolOp.getScope(),
                              protocolOp.getDereferencePolicy(),
@@ -2296,7 +2295,6 @@
                              protocolOp.getTypesOnly(), protocolOp.getFilter(),
                              protocolOp.getAttributes());
 
-
     // Add the operation into the work queue.
     try
     {
@@ -2346,7 +2344,6 @@
 
     unbindOp.run();
 
-
     // The client connection will never be valid after an unbind.
     return false;
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java
index 91bdc28..969c1e0 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/Historical.java
@@ -52,7 +52,7 @@
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
-
+import org.opends.server.workflowelement.localbackend.*;
 
 /**
  * This class is used to store historical information that is
@@ -146,7 +146,7 @@
    *
    * @param modifyOperation the modification.
    */
-  public void generateState(ModifyOperation modifyOperation)
+  public void generateState(LocalBackendModifyOperation modifyOperation)
   {
     List<Modification> mods = modifyOperation.getModifications();
     Entry modifiedEntry = modifyOperation.getModifiedEntry();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
index 27a2466..5c02af2 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
@@ -41,11 +41,8 @@
 import org.opends.server.api.RestoreTaskListener;
 import org.opends.server.api.SynchronizationProvider;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.AddOperation;
-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.types.BackupConfig;
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DN;
@@ -58,6 +55,7 @@
 import org.opends.server.types.RestoreConfig;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SynchronizationProviderResult;
+import org.opends.server.workflowelement.localbackend.*;
 
 import static org.opends.server.messages.ReplicationMessages.*;
 
@@ -214,7 +212,7 @@
    * {@inheritDoc}
    */
   @Override
-  public void doPostOperation(AddOperation addOperation)
+  public void doPostOperation(LocalBackendAddOperation addOperation)
   {
     DN dn = addOperation.getEntryDN();
     genericPostOperation(addOperation, dn);
@@ -225,7 +223,7 @@
    * {@inheritDoc}
    */
   @Override
-  public void doPostOperation(DeleteOperation deleteOperation)
+  public void doPostOperation(LocalBackendDeleteOperation deleteOperation)
   {
     DN dn = deleteOperation.getEntryDN();
     genericPostOperation(deleteOperation, dn);
@@ -245,7 +243,7 @@
    * {@inheritDoc}
    */
   @Override
-  public void doPostOperation(ModifyOperation modifyOperation)
+  public void doPostOperation(LocalBackendModifyOperation modifyOperation)
   {
     DN dn = modifyOperation.getEntryDN();
     genericPostOperation(modifyOperation, dn);
@@ -256,7 +254,7 @@
    */
   @Override
   public SynchronizationProviderResult handleConflictResolution(
-                                                ModifyOperation modifyOperation)
+    LocalBackendModifyOperation modifyOperation)
   {
     ReplicationDomain domain =
       findDomain(modifyOperation.getEntryDN(), modifyOperation);
@@ -271,7 +269,7 @@
    */
   @Override
   public SynchronizationProviderResult handleConflictResolution(
-      AddOperation addOperation) throws DirectoryException
+      LocalBackendAddOperation addOperation) throws DirectoryException
   {
     ReplicationDomain domain =
       findDomain(addOperation.getEntryDN(), addOperation);
@@ -286,7 +284,7 @@
    */
   @Override
   public SynchronizationProviderResult handleConflictResolution(
-      DeleteOperation deleteOperation) throws DirectoryException
+      LocalBackendDeleteOperation deleteOperation) throws DirectoryException
   {
     ReplicationDomain domain =
       findDomain(deleteOperation.getEntryDN(), deleteOperation);
@@ -316,7 +314,7 @@
    */
   @Override
   public SynchronizationProviderResult
-      doPreOperation(ModifyOperation modifyOperation)
+      doPreOperation(LocalBackendModifyOperation modifyOperation)
   {
     DN operationDN = modifyOperation.getEntryDN();
     ReplicationDomain domain = findDomain(operationDN, modifyOperation);
@@ -343,7 +341,7 @@
    */
   @Override
   public SynchronizationProviderResult doPreOperation(
-      DeleteOperation deleteOperation) throws DirectoryException
+      LocalBackendDeleteOperation deleteOperation) throws DirectoryException
   {
     return new SynchronizationProviderResult(true);
   }
@@ -362,7 +360,8 @@
    * {@inheritDoc}
    */
   @Override
-  public SynchronizationProviderResult doPreOperation(AddOperation addOperation)
+  public SynchronizationProviderResult doPreOperation(
+      LocalBackendAddOperation addOperation)
   {
     ReplicationDomain domain =
       findDomain(addOperation.getEntryDN(), addOperation);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java
index 0579de3..7702c2d 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java
@@ -36,7 +36,7 @@
 import java.util.List;
 
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.internal.InternalSearchOperation;
@@ -218,15 +218,14 @@
     ArrayList<RawModification> mods = new ArrayList<RawModification>(1);
     mods.add(mod);
 
-    ModifyOperation op =
-      new ModifyOperation(conn, InternalClientConnection.nextOperationID(),
+    ModifyOperationBasis op =
+      new ModifyOperationBasis(conn, InternalClientConnection.nextOperationID(),
           InternalClientConnection.nextMessageID(),
           new ArrayList<Control>(0), asn1BaseDn,
           mods);
     op.setInternalOperation(true);
     op.setSynchronizationOperation(true);
     op.setDontSynchronize(true);
-
     op.run();
 
     ResultCode result = op.getResultCode();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
index ed7b98a..29f3aa6 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
@@ -68,6 +68,7 @@
 import org.opends.server.core.LockFileManager;
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1Exception;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.internal.InternalSearchOperation;
@@ -93,6 +94,7 @@
 import org.opends.server.tasks.InitializeTargetTask;
 import org.opends.server.tasks.InitializeTask;
 import org.opends.server.tasks.TaskUtils;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValue;
@@ -115,6 +117,7 @@
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchScope;
 import org.opends.server.types.SynchronizationProviderResult;
+import org.opends.server.workflowelement.localbackend.*;
 
 /**
  *  This class implements the bulk part of the.of the Directory Server side
@@ -420,7 +423,7 @@
    *         can continue.
    */
   public SynchronizationProviderResult handleConflictResolution(
-      DeleteOperation deleteOperation)
+      LocalBackendDeleteOperation deleteOperation)
   {
     if ((!deleteOperation.isSynchronizationOperation())
         && (!brokerIsConnected(deleteOperation)))
@@ -674,7 +677,7 @@
    * @return code indicating is operation must proceed
    */
   public SynchronizationProviderResult handleConflictResolution(
-                                                ModifyOperation modifyOperation)
+      LocalBackendModifyOperation modifyOperation)
   {
     if ((!modifyOperation.isSynchronizationOperation())
         && (!brokerIsConnected(modifyOperation)))
@@ -1203,8 +1206,7 @@
         op.setInternalOperation(true);
         op.setSynchronizationOperation(true);
         changeNumber = OperationContext.getChangeNumber(op);
-
-        op.run();
+        ((AbstractOperation)op).run();
 
         ResultCode result = op.getResultCode();
 
@@ -2863,7 +2865,7 @@
   public void synchronizeModifications(List<Modification> modifications)
   {
     Operation op =
-      new ModifyOperation(InternalClientConnection.getRootConnection(),
+      new ModifyOperationBasis(InternalClientConnection.getRootConnection(),
                           InternalClientConnection.nextOperationID(),
                           InternalClientConnection.nextMessageID(),
                           null, DirectoryServer.getSchemaDN(),
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java
index 299bcfa..84625ad 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/AddMsg.java
@@ -27,6 +27,7 @@
 package org.opends.server.replication.protocol;
 
 import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.asn1.ASN1Element;
 import org.opends.server.protocols.asn1.ASN1Exception;
@@ -199,7 +200,7 @@
       attr.add(LDAPAttribute.decode(elem));
     }
 
-    AddOperation add =  new AddOperation(connection,
+    AddOperationBasis add =  new AddOperationBasis(connection,
                             InternalClientConnection.nextOperationID(),
                             InternalClientConnection.nextMessageID(), null,
                             new ASN1OctetString(newDn), attr);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java
index cea0321..d8ba12b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java
@@ -32,6 +32,7 @@
 import java.util.zip.DataFormatException;
 
 import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.replication.common.ChangeNumber;
@@ -90,7 +91,7 @@
   public Operation createOperation(InternalClientConnection connection,
       String newDn)
   {
-    DeleteOperation del =  new DeleteOperation(connection,
+    DeleteOperationBasis del =  new DeleteOperationBasis(connection,
                                InternalClientConnection.nextOperationID(),
                                InternalClientConnection.nextMessageID(), null,
                                new ASN1OctetString(newDn));
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ModifyMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ModifyMsg.java
index aff4180..52fb3e2 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ModifyMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ModifyMsg.java
@@ -29,6 +29,7 @@
 import static org.opends.server.replication.protocol.OperationContext.*;
 
 import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1Exception;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.ldap.LDAPAttribute;
@@ -148,7 +149,7 @@
     for (ASN1Element elem : mods)
       ldapmods.add(LDAPModification.decode(elem));
 
-    ModifyOperation mod = new ModifyOperation(connection,
+    ModifyOperationBasis mod = new ModifyOperationBasis(connection,
                                InternalClientConnection.nextOperationID(),
                                InternalClientConnection.nextMessageID(), null,
                                new ASN1OctetString(newDn), ldapmods);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/AbstractOperation.java b/opendj-sdk/opends/src/server/org/opends/server/types/AbstractOperation.java
new file mode 100644
index 0000000..163fddd
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/AbstractOperation.java
@@ -0,0 +1,691 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types;
+
+
+
+import static org.opends.server.core.CoreConstants.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.types.operation.PostResponseOperation;
+import org.opends.server.types.operation.PreParseOperation;
+import org.opends.server.core.DirectoryServer;
+
+
+
+/**
+ * This class defines a generic operation that may be processed by the
+ * Directory Server.  Specific subclasses should implement specific
+ * functionality appropriate for the type of operation.
+ * <BR><BR>
+ * Note that this class is not intended to be subclassed by any
+ * third-party code outside of the OpenDS project.  It should only be
+ * extended by the operation types included in the
+ * {@code org.opends.server.core} package.
+ */
+public abstract class AbstractOperation
+       implements Operation, PreParseOperation, PostResponseOperation,
+                  Runnable
+{
+  /**
+   * The set of response controls that will always be returned for
+   * an abandon operation.
+   */
+  protected static final List<Control> NO_RESPONSE_CONTROLS =
+       new ArrayList<Control>(0);
+
+  /**
+   * The client connection with which this operation is associated.
+   */
+  protected final ClientConnection clientConnection;
+
+
+  /**
+   * The message ID for this operation.
+   */
+  protected final int messageID;
+
+
+
+  /**
+   * The operation ID for this operation.
+   */
+  protected final long operationID;
+
+
+
+  // Indicates whether this is an internal operation triggered within
+  // the server itself rather than requested by an external client.
+  private boolean isInternalOperation;
+
+  // Indicates whether this operation is involved in data
+  // synchronization processing.
+  private boolean isSynchronizationOperation;
+
+  // The cancel result for this operation.
+  private CancelResult cancelResult;
+
+  // The matched DN for this operation.
+  private DN matchedDN;
+
+  // The entry for the authorization identify for this operation.
+  private Entry authorizationEntry;
+
+  // A set of attachments associated with this operation that might
+  // be used by various components during its processing.
+  private Map<String,Object> attachments;
+
+  // The set of controls included in the request from the client.
+  private List<Control> requestControls;
+
+  // The set of referral URLs for this operation.
+  private List<String> referralURLs;
+
+  // The result code for this operation.
+  private ResultCode resultCode;
+
+  // Additional information that should be included in the log but
+  // not sent to the client.
+  private StringBuilder additionalLogMessage;
+
+  // The error message for this operation that should be included in
+  // the log and in the response to the client.
+  private StringBuilder errorMessage;
+
+  // Indicates whether this operation nneds to be synchronized to
+  // other copies of the data.
+  private boolean dontSynchronizeFlag;
+
+  // The time that processing started on this operation.
+  private long processingStartTime;
+
+  // The time that processing ended on this operation.
+  private long processingStopTime;
+
+  /**
+   * Creates a new operation with the provided information.
+   *
+   * @param  clientConnection  The client connection with which this
+   *                           operation is associated.
+   * @param  operationID       The identifier assigned to this
+   *                           operation for the client connection.
+   * @param  messageID         The message ID of the request with
+   *                           which this operation is associated.
+   * @param  requestControls   The set of controls included in the
+   *                           request.
+   */
+  protected AbstractOperation(ClientConnection clientConnection,
+                      long operationID,
+                      int messageID, List<Control> requestControls)
+  {
+    this.clientConnection = clientConnection;
+    this.operationID      = operationID;
+    this.messageID        = messageID;
+
+    if (requestControls == null)
+    {
+      this.requestControls = new ArrayList<Control>(0);
+    }
+    else
+    {
+      this.requestControls  = requestControls;
+    }
+
+    resultCode                 = ResultCode.UNDEFINED;
+    additionalLogMessage       = new StringBuilder();
+    errorMessage               = new StringBuilder();
+    attachments                = new HashMap<String,Object>();
+    matchedDN                  = null;
+    referralURLs               = null;
+    cancelResult               = null;
+    isInternalOperation        = false;
+    isSynchronizationOperation = false;
+    authorizationEntry         =
+         clientConnection.getAuthenticationInfo().
+          getAuthorizationEntry();
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract OperationType getOperationType();
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract void disconnectClient(
+      DisconnectReason disconnectReason,
+      boolean sendNotification,
+      String message, int messageID);
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String[][] getCommonLogElements()
+  {
+    // Note that no debugging will be done in this method because
+    // it is a likely candidate for being called by the logging
+    // subsystem.
+
+    return new String[][]
+    {
+      new String[] { LOG_ELEMENT_CONNECTION_ID,
+                     String.valueOf(getConnectionID()) },
+      new String[] { LOG_ELEMENT_OPERATION_ID,
+          String.valueOf(operationID) },
+      new String[] { LOG_ELEMENT_MESSAGE_ID,
+          String.valueOf(messageID) }
+    };
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract String[][] getRequestLogElements();
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract String[][] getResponseLogElements();
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ClientConnection getClientConnection()
+  {
+    return clientConnection;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getConnectionID()
+  {
+    return clientConnection.getConnectionID();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getOperationID()
+  {
+    return operationID;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final int getMessageID()
+  {
+    return messageID;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final List<Control> getRequestControls()
+  {
+    return requestControls;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void addRequestControl(Control control)
+  {
+    requestControls.add(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void removeRequestControl(Control control)
+  {
+    requestControls.remove(control);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract List<Control> getResponseControls();
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract void addResponseControl(Control control);
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract void removeResponseControl(Control control);
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ResultCode getResultCode()
+  {
+    return resultCode;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setResultCode(ResultCode resultCode)
+  {
+    this.resultCode = resultCode;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final StringBuilder getErrorMessage()
+  {
+    return errorMessage;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setErrorMessage(StringBuilder errorMessage)
+  {
+    if (errorMessage == null)
+    {
+      this.errorMessage = new StringBuilder();
+    }
+    else
+    {
+      this.errorMessage = errorMessage;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void appendErrorMessage(String message)
+  {
+    if (errorMessage == null)
+    {
+      errorMessage = new StringBuilder(message);
+    }
+    else
+    {
+      if (errorMessage.length() > 0)
+      {
+        errorMessage.append("  ");
+      }
+
+      errorMessage.append(message);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final StringBuilder getAdditionalLogMessage()
+  {
+    return additionalLogMessage;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setAdditionalLogMessage(StringBuilder
+      additionalLogMessage)
+  {
+    if (additionalLogMessage == null)
+    {
+      this.additionalLogMessage = new StringBuilder();
+    }
+    else
+    {
+      this.additionalLogMessage = additionalLogMessage;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void appendAdditionalLogMessage(String message)
+  {
+    if (additionalLogMessage == null)
+    {
+      additionalLogMessage = new StringBuilder(message);
+    }
+    else
+    {
+      additionalLogMessage.append(message);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DN getMatchedDN()
+  {
+    return matchedDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setMatchedDN(DN matchedDN)
+  {
+    this.matchedDN = matchedDN;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final List<String> getReferralURLs()
+  {
+    return referralURLs;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setReferralURLs(List<String> referralURLs)
+  {
+    this.referralURLs = referralURLs;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setResponseData(DirectoryException
+      directoryException)
+  {
+    this.resultCode   = directoryException.getResultCode();
+    this.matchedDN    = directoryException.getMatchedDN();
+    this.referralURLs = directoryException.getReferralURLs();
+
+    appendErrorMessage(directoryException.getErrorMessage());
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean isInternalOperation()
+  {
+    return isInternalOperation;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setInternalOperation(boolean isInternalOperation)
+  {
+    this.isInternalOperation = isInternalOperation;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean isSynchronizationOperation()
+  {
+    return isSynchronizationOperation;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setSynchronizationOperation(
+                         boolean isSynchronizationOperation)
+  {
+    this.isSynchronizationOperation = isSynchronizationOperation;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setDontSynchronize(boolean dontSynchronize)
+  {
+    this.dontSynchronizeFlag = dontSynchronize;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Entry getAuthorizationEntry()
+  {
+    return authorizationEntry;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setAuthorizationEntry(Entry authorizationEntry)
+  {
+    this.authorizationEntry = authorizationEntry;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final DN getAuthorizationDN()
+  {
+    if (authorizationEntry == null)
+    {
+      return DN.nullDN();
+    }
+    else
+    {
+      return authorizationEntry.getDN();
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Map<String,Object> getAttachments()
+  {
+    return attachments;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Object getAttachment(String name)
+  {
+    return attachments.get(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Object removeAttachment(String name)
+  {
+    return attachments.remove(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Object setAttachment(String name, Object value)
+  {
+    return attachments.put(name, value);
+  }
+
+  /**
+   * Performs the work of actually processing this operation.
+   * This should include all processing for the operation, including
+   * invoking plugins, logging messages, performing access control,
+   * managing synchronization, and any other work that might need to
+   * be done in the course of processing.
+   */
+  /*public abstract void run();*/
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void operationCompleted()
+  {
+    // Notify the client connection that this operation is complete
+    // and that it no longer needs to be retained.
+    clientConnection.removeOperationInProgress(messageID);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract CancelResult cancel(CancelRequest cancelRequest);
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract boolean setCancelRequest(CancelRequest
+      cancelRequest);
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract CancelRequest getCancelRequest();
+
+  /**
+   * {@inheritDoc}
+   */
+  public final CancelResult getCancelResult()
+  {
+    return cancelResult;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setCancelResult(CancelResult cancelResult)
+  {
+    this.cancelResult = cancelResult;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void indicateCancelled(CancelRequest cancelRequest)
+  {
+    setCancelResult(CancelResult.CANCELED);
+
+    if (cancelRequest.notifyOriginalRequestor() ||
+        DirectoryServer.notifyAbandonedOperations())
+    {
+      setResultCode(ResultCode.CANCELED);
+
+      String cancelReason = cancelRequest.getCancelReason();
+      if (cancelReason != null)
+      {
+        appendErrorMessage(cancelReason);
+      }
+
+      clientConnection.sendResponse(this);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String toString()
+  {
+    StringBuilder buffer = new StringBuilder();
+    toString(buffer);
+    return buffer.toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract void toString(StringBuilder buffer);
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean dontSynchronize()
+  {
+    return dontSynchronizeFlag;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setAttachments(Map<String, Object> attachments){
+    this.attachments = attachments;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getProcessingStartTime()
+  {
+    return processingStartTime;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getProcessingStopTime()
+  {
+    return processingStopTime;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setProcessingStopTime()
+  {
+    this.processingStopTime = System.currentTimeMillis();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final void setProcessingStartTime()
+  {
+    processingStartTime = System.currentTimeMillis();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public final long getProcessingTime()
+  {
+    return (processingStopTime - processingStartTime);
+  }
+
+  /**
+   * Performs the work of actually processing this operation.  This
+   * should include all processing for the operation, including
+   * invoking pre-parse and post-response plugins, logging messages
+   * and any other work that might need to be done in the course of
+   * processing.
+   */
+  public abstract void run();
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/Operation.java b/opendj-sdk/opends/src/server/org/opends/server/types/Operation.java
index 2e53740..23020cb 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/Operation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/Operation.java
@@ -28,156 +28,31 @@
 
 
 
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.opends.server.api.ClientConnection;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.types.operation.PostOperationOperation;
-import org.opends.server.types.operation.PostResponseOperation;
-import org.opends.server.types.operation.PreOperationOperation;
-import org.opends.server.types.operation.PreParseOperation;
-
-import static org.opends.server.core.CoreConstants.*;
-
 
 
 
 /**
- * This class defines a generic operation that may be processed by the
- * Directory Server.  Specific subclasses should implement specific
- * functionality appropriate for the type of operation.
+ * This interface defines a generic operation that may be processed by
+ * the Directory Server.  Specific subclasses should implement
+ * specific functionality appropriate for the type of operation.
  * <BR><BR>
  * Note that this class is not intended to be subclassed by any
  * third-party code outside of the OpenDS project.  It should only be
  * extended by the operation types included in the
  * {@code org.opends.server.core} package.
  */
-public abstract class Operation
-       implements PreParseOperation, PreOperationOperation,
-                  PostOperationOperation, PostResponseOperation,
-                  Runnable
+public interface Operation
 {
   /**
-   * The set of response controls that will always be returned for an
-   * abandon operation.
+   * Identifier used to get the local operation [if any] in the
+   * attachments.
    */
-  protected static final List<Control> NO_RESPONSE_CONTROLS =
-       new ArrayList<Control>(0);
-
-
-
-  /**
-   * The client connection with which this operation is associated.
-   */
-  protected final ClientConnection clientConnection;
-
-
-
-  /**
-   * The message ID for this operation.
-   */
-  protected final int messageID;
-
-
-
-  /**
-   * The operation ID for this operation.
-   */
-  protected final long operationID;
-
-
-
-  // Indicates whether this is an internal operation triggered within
-  // the server itself rather than requested by an external client.
-  private boolean isInternalOperation;
-
-  // Indicates whether this operation is involved in data
-  // synchronization processing.
-  private boolean isSynchronizationOperation;
-
-  // The cancel result for this operation.
-  private CancelResult cancelResult;
-
-  // The matched DN for this operation.
-  private DN matchedDN;
-
-  // The entry for the authorization identify for this operation.
-  private Entry authorizationEntry;
-
-  // A set of attachments associated with this operation that might be
-  // used by various components during its processing.
-  private Map<String,Object> attachments;
-
-  // The set of controls included in the request from the client.
-  private List<Control> requestControls;
-
-  // The set of referral URLs for this operation.
-  private List<String> referralURLs;
-
-  // The result code for this operation.
-  private ResultCode resultCode;
-
-  // Additional information that should be included in the log but not
-  // sent to the client.
-  private StringBuilder additionalLogMessage;
-
-  // The error message for this operation that should be included in
-  // the log and in the response to the client.
-  private StringBuilder errorMessage;
-
-  // Indicates whether this operation nneds to be synchronized to
-  // other copies of the data.
-  private boolean dontSynchronizeFlag;
-
-
-
-  /**
-   * Creates a new operation with the provided information.
-   *
-   * @param  clientConnection  The client connection with which this
-   *                           operation is associated.
-   * @param  operationID       The identifier assigned to this
-   *                           operation for the client connection.
-   * @param  messageID         The message ID of the request with
-   *                           which this operation is associated.
-   * @param  requestControls   The set of controls included in the
-   *                           request.
-   */
-  protected Operation(ClientConnection clientConnection,
-                      long operationID, int messageID,
-                      List<Control> requestControls)
-  {
-    this.clientConnection = clientConnection;
-    this.operationID      = operationID;
-    this.messageID        = messageID;
-
-    if (requestControls == null)
-    {
-      this.requestControls = new ArrayList<Control>(0);
-    }
-    else
-    {
-      this.requestControls  = requestControls;
-    }
-
-    resultCode                 = ResultCode.UNDEFINED;
-    additionalLogMessage       = new StringBuilder();
-    errorMessage               = new StringBuilder();
-    attachments                = new HashMap<String,Object>();
-    matchedDN                  = null;
-    referralURLs               = null;
-    cancelResult               = null;
-    isInternalOperation        = false;
-    isSynchronizationOperation = false;
-    authorizationEntry         =
-         clientConnection.getAuthenticationInfo().
-              getAuthorizationEntry();
-  }
-
-
+  public static final String LOCALBACKENDOPERATIONS =
+    "LocalBackendOperations";
 
   /**
    * Retrieves the operation type for this operation.
@@ -186,8 +61,6 @@
    */
   public abstract OperationType getOperationType();
 
-
-
   /**
    * Terminates the client connection being used to process this
    * operation.  If this is called by a plugin, then that plugin must
@@ -213,8 +86,6 @@
                             boolean sendNotification, String message,
                             int messageID);
 
-
-
   /**
    * Retrieves a set of standard elements that should be logged in all
    * requests and responses for all types of operations.  Each element
@@ -226,20 +97,7 @@
    * @return  A standard set of elements that should be logged in
    *          requests and responses for all types of operations.
    */
-  public final String[][] getCommonLogElements()
-  {
-    return new String[][]
-    {
-      new String[] { LOG_ELEMENT_CONNECTION_ID,
-                     String.valueOf(getConnectionID()) },
-      new String[] { LOG_ELEMENT_OPERATION_ID,
-                     String.valueOf(operationID) },
-      new String[] { LOG_ELEMENT_MESSAGE_ID,
-                     String.valueOf(messageID) }
-    };
-  }
-
-
+  public abstract String[][] getCommonLogElements();
 
   /**
    * Retrieves a standard set of elements that should be logged in
@@ -254,8 +112,6 @@
    */
   public abstract String[][] getRequestLogElements();
 
-
-
   /**
    * Retrieves a standard set of elements that should be logged in
    * responses for this type of operation.  Each element in the array
@@ -269,9 +125,6 @@
    */
   public abstract String[][] getResponseLogElements();
 
-
-
-
   /**
    * Retrieves the client connection with which this operation is
    * associated.
@@ -279,12 +132,7 @@
    * @return  The client connection with which this operation is
    *          associated.
    */
-  public final ClientConnection getClientConnection()
-  {
-    return clientConnection;
-  }
-
-
+  public abstract ClientConnection getClientConnection();
 
   /**
    * Retrieves the unique identifier that is assigned to the client
@@ -293,36 +141,21 @@
    * @return  The unique identifier that is assigned to the client
    *          connection that submitted this operation.
    */
-  public final long getConnectionID()
-  {
-    return clientConnection.getConnectionID();
-  }
-
-
+  public abstract long getConnectionID();
 
   /**
    * Retrieves the operation ID for this operation.
    *
    * @return  The operation ID for this operation.
    */
-  public final long getOperationID()
-  {
-    return operationID;
-  }
-
-
+  public abstract long getOperationID();
 
   /**
    * Retrieves the message ID assigned to this operation.
    *
    * @return  The message ID assigned to this operation.
    */
-  public final int getMessageID()
-  {
-    return messageID;
-  }
-
-
+  public abstract int getMessageID();
 
   /**
    * Retrieves the set of controls included in the request from the
@@ -331,12 +164,7 @@
    * @return  The set of controls included in the request from the
    *          client.
    */
-  public final List<Control> getRequestControls()
-  {
-    return requestControls;
-  }
-
-
+  public abstract List<Control> getRequestControls();
 
   /**
    * Adds the provided control to the set of request controls for this
@@ -345,12 +173,7 @@
    * @param  control  The control to add to the set of request
    *                  controls for this operation.
    */
-  public final void addRequestControl(Control control)
-  {
-    requestControls.add(control);
-  }
-
-
+  public abstract void addRequestControl(Control control);
 
   /**
    * Removes the provided control from the set of request controls for
@@ -360,12 +183,7 @@
    * @param  control  The control to remove from the set of request
    *                  controls for this operation.
    */
-  public final void removeRequestControl(Control control)
-  {
-    requestControls.remove(control);
-  }
-
-
+  public abstract void removeRequestControl(Control control);
 
   /**
    * Retrieves the set of controls to include in the response to the
@@ -376,8 +194,6 @@
    */
   public abstract List<Control> getResponseControls();
 
-
-
   /**
    * Adds the provided control to the set of controls to include in
    * the response to the client.  This method may not be called by
@@ -388,8 +204,6 @@
    */
   public abstract void addResponseControl(Control control);
 
-
-
   /**
    * Removes the provided control from the set of controls to include
    * in the response to the client.  This method may not be called by
@@ -400,8 +214,6 @@
    */
   public abstract void removeResponseControl(Control control);
 
-
-
   /**
    * Retrieves the result code for this operation.
    *
@@ -409,12 +221,7 @@
    *          {@code UNDEFINED} if the operation has not yet
    *          completed.
    */
-  public final ResultCode getResultCode()
-  {
-    return resultCode;
-  }
-
-
+  public abstract ResultCode getResultCode();
 
   /**
    * Specifies the result code for this operation.  This method may
@@ -422,12 +229,7 @@
    *
    * @param  resultCode  The result code for this operation.
    */
-  public final void setResultCode(ResultCode resultCode)
-  {
-    this.resultCode = resultCode;
-  }
-
-
+  public abstract void setResultCode(ResultCode resultCode);
 
   /**
    * Retrieves the error message for this operation.  Its contents may
@@ -436,12 +238,7 @@
    *
    * @return  The error message for this operation.
    */
-  public final StringBuilder getErrorMessage()
-  {
-    return errorMessage;
-  }
-
-
+  public abstract StringBuilder getErrorMessage();
 
   /**
    * Specifies the error message for this operation.  This method may
@@ -449,19 +246,7 @@
    *
    * @param  errorMessage  The error message for this operation.
    */
-  public final void setErrorMessage(StringBuilder errorMessage)
-  {
-    if (errorMessage == null)
-    {
-      this.errorMessage = new StringBuilder();
-    }
-    else
-    {
-      this.errorMessage = errorMessage;
-    }
-  }
-
-
+  public abstract void setErrorMessage(StringBuilder errorMessage);
 
   /**
    * Appends the provided message to the error message buffer.  If the
@@ -472,24 +257,7 @@
    * @param  message  The message to append to the error message
    *                  buffer.
    */
-  public final void appendErrorMessage(String message)
-  {
-    if (errorMessage == null)
-    {
-      errorMessage = new StringBuilder(message);
-    }
-    else
-    {
-      if (errorMessage.length() > 0)
-      {
-        errorMessage.append("  ");
-      }
-
-      errorMessage.append(message);
-    }
-  }
-
-
+  public abstract void appendErrorMessage(String message);
 
   /**
    * Retrieves the additional log message for this operation, which
@@ -500,12 +268,7 @@
    *
    * @return  The additional log message for this operation.
    */
-  public final StringBuilder getAdditionalLogMessage()
-  {
-    return additionalLogMessage;
-  }
-
-
+  public abstract StringBuilder getAdditionalLogMessage();
 
   /**
    * Specifies the additional log message for this operation, which
@@ -516,20 +279,8 @@
    * @param  additionalLogMessage  The additional log message for this
    *                               operation.
    */
-  public final void setAdditionalLogMessage(StringBuilder
-                                                 additionalLogMessage)
-  {
-    if (additionalLogMessage == null)
-    {
-      this.additionalLogMessage = new StringBuilder();
-    }
-    else
-    {
-      this.additionalLogMessage = additionalLogMessage;
-    }
-  }
-
-
+  public abstract void setAdditionalLogMessage(
+      StringBuilder additionalLogMessage);
 
   /**
    * Appends the provided message to the additional log information
@@ -539,19 +290,7 @@
    * @param  message  The message that should be appended to the
    *                  additional log information for this operation.
    */
-  public final void appendAdditionalLogMessage(String message)
-  {
-    if (additionalLogMessage == null)
-    {
-      additionalLogMessage = new StringBuilder(message);
-    }
-    else
-    {
-      additionalLogMessage.append(message);
-    }
-  }
-
-
+  public abstract void appendAdditionalLogMessage(String message);
 
   /**
    * Retrieves the matched DN for this operation.
@@ -560,12 +299,7 @@
    *          the operation has not yet completed or does not have a
    *          matched DN.
    */
-  public final DN getMatchedDN()
-  {
-    return matchedDN;
-  }
-
-
+  public abstract DN getMatchedDN();
 
   /**
    * Specifies the matched DN for this operation.  This may not be
@@ -573,12 +307,7 @@
    *
    * @param  matchedDN  The matched DN for this operation.
    */
-  public final void setMatchedDN(DN matchedDN)
-  {
-    this.matchedDN = matchedDN;
-  }
-
-
+  public abstract void setMatchedDN(DN matchedDN);
 
   /**
    * Retrieves the set of referral URLs for this operation.  Its
@@ -588,12 +317,7 @@
    *          {@code null} if the operation is not yet complete or
    *          does not have a set of referral URLs.
    */
-  public final List<String> getReferralURLs()
-  {
-    return referralURLs;
-  }
-
-
+  public abstract List<String> getReferralURLs();
 
   /**
    * Specifies the set of referral URLs for this operation.  This may
@@ -602,12 +326,7 @@
    * @param  referralURLs  The set of referral URLs for this
    *                       operation.
    */
-  public final void setReferralURLs(List<String> referralURLs)
-  {
-    this.referralURLs = referralURLs;
-  }
-
-
+  public abstract void setReferralURLs(List<String> referralURLs);
 
   /**
    * Sets the response elements for this operation based on the
@@ -618,17 +337,8 @@
    *                             information to use for the response
    *                             elements.
    */
-  public final void setResponseData(
-                         DirectoryException directoryException)
-  {
-    this.resultCode   = directoryException.getResultCode();
-    this.matchedDN    = directoryException.getMatchedDN();
-    this.referralURLs = directoryException.getReferralURLs();
-
-    appendErrorMessage(directoryException.getErrorMessage());
-  }
-
-
+  public abstract void setResponseData(
+      DirectoryException directoryException);
 
   /**
    * Indicates whether this is an internal operation rather than one
@@ -637,12 +347,7 @@
    * @return  {@code true} if this is an internal operation, or
    *          {@code false} if it is not.
    */
-  public final boolean isInternalOperation()
-  {
-    return isInternalOperation;
-  }
-
-
+  public abstract boolean isInternalOperation();
 
   /**
    * Specifies whether this is an internal operation rather than one
@@ -654,12 +359,8 @@
    *                              that was requested by an external
    *                              client.
    */
-  public final void setInternalOperation(boolean isInternalOperation)
-  {
-    this.isInternalOperation = isInternalOperation;
-  }
-
-
+  public abstract void setInternalOperation(boolean
+      isInternalOperation);
 
   /**
    * Indicates whether this is a synchronization operation rather than
@@ -668,12 +369,7 @@
    * @return  {@code true} if this is a data synchronization
    *          operation, or {@code false} if it is not.
    */
-  public final boolean isSynchronizationOperation()
-  {
-    return isSynchronizationOperation;
-  }
-
-
+  public abstract boolean isSynchronizationOperation();
 
   /**
    * Specifies whether this is a synchronization operation rather than
@@ -686,12 +382,8 @@
    *                                     requested by an external
    *                                     client.
    */
-  public final void setSynchronizationOperation(
-                         boolean isSynchronizationOperation)
-  {
-    this.isSynchronizationOperation = isSynchronizationOperation;
-  }
-
+  public abstract void setSynchronizationOperation(
+      boolean isSynchronizationOperation);
 
   /**
    * Specifies whether this operation must be synchronized to other
@@ -701,10 +393,7 @@
    *                          synchronized to other copies
    *                          of the data.
    */
-  public final void setDontSynchronize(boolean dontSynchronize)
-  {
-    this.dontSynchronizeFlag = dontSynchronize;
-  }
+  public abstract void setDontSynchronize(boolean dontSynchronize);
 
   /**
    * Retrieves the entry for the user that should be considered the
@@ -722,12 +411,7 @@
    *          {@code null} if the authorization identity should be the
    *          unauthenticated  user.
    */
-  public final Entry getAuthorizationEntry()
-  {
-    return authorizationEntry;
-  }
-
-
+  public abstract Entry getAuthorizationEntry();
 
   /**
    * Provides the entry for the user that should be considered the
@@ -740,12 +424,8 @@
    *                             if it should be the unauthenticated
    *                             user.
    */
-  public final void setAuthorizationEntry(Entry authorizationEntry)
-  {
-    this.authorizationEntry = authorizationEntry;
-  }
-
-
+  public abstract void setAuthorizationEntry(Entry
+      authorizationEntry);
 
   /**
    * Retrieves the authorization DN for this operation.  In many
@@ -760,19 +440,7 @@
    * @return  The authorization DN for this operation, or the null DN
    *          if it should be the unauthenticated user..
    */
-  public final DN getAuthorizationDN()
-  {
-    if (authorizationEntry == null)
-    {
-      return DN.nullDN();
-    }
-    else
-    {
-      return authorizationEntry.getDN();
-    }
-  }
-
-
+  public abstract DN getAuthorizationDN();
 
   /**
    * Retrieves the set of attachments defined for this operation, as a
@@ -780,12 +448,7 @@
    *
    * @return  The set of attachments defined for this operation.
    */
-  public final Map<String,Object> getAttachments()
-  {
-    return attachments;
-  }
-
-
+  public abstract Map<String, Object> getAttachments();
 
   /**
    * Retrieves the attachment with the specified name.
@@ -796,12 +459,7 @@
    * @return  The requested attachment object, or {@code null} if it
    *          does not exist.
    */
-  public final Object getAttachment(String name)
-  {
-    return attachments.get(name);
-  }
-
-
+  public abstract Object getAttachment(String name);
 
   /**
    * Removes the attachment with the specified name.
@@ -812,12 +470,7 @@
    * @return  The attachment that was removed, or {@code null} if it
    *          does not exist.
    */
-  public final Object removeAttachment(String name)
-  {
-    return attachments.remove(name);
-  }
-
-
+  public abstract Object removeAttachment(String name);
 
   /**
    * Sets the value of the specified attachment.  If an attachment
@@ -831,12 +484,7 @@
    *          name, or {@code null} if there was previously no such
    *          attachment.
    */
-  public final Object setAttachment(String name, Object value)
-  {
-    return attachments.put(name, value);
-  }
-
-
+  public abstract Object setAttachment(String name, Object value);
 
   /**
    * Retrieves the time that processing started for this operation.
@@ -845,8 +493,6 @@
    */
   public abstract long getProcessingStartTime();
 
-
-
   /**
    * Retrieves the time that processing stopped for this operation.
    * This will actually hold a time immediately before the response
@@ -856,8 +502,6 @@
    */
   public abstract long getProcessingStopTime();
 
-
-
   /**
    * Retrieves the length of time in milliseconds that the server
    * spent processing this operation.  This should not be called until
@@ -868,32 +512,12 @@
    */
   public abstract long getProcessingTime();
 
-
-
-  /**
-   * Performs the work of actually processing this operation.  This
-   * should include all processing for the operation, including
-   * invoking plugins, logging messages, performing access control,
-   * managing synchronization, and any other work that might need to
-   * be done in the course of processing.
-   */
-  public abstract void run();
-
-
-
   /**
    * Indicates that processing on this operation has completed
    * successfully and that the client should perform any associated
    * cleanup work.
    */
-  public final void operationCompleted()
-  {
-    // Notify the client connection that this operation is complete
-    // and that it no longer needs to be retained.
-    clientConnection.removeOperationInProgress(messageID);
-  }
-
-
+  public abstract void operationCompleted();
 
   /**
    * Attempts to cancel this operation before processing has
@@ -907,8 +531,6 @@
    */
   public abstract CancelResult cancel(CancelRequest cancelRequest);
 
-
-
   /**
    * Sets the cancel request for this operation, if applicable.  This
    * should only be used for testing purposes (e.g., for ensuring a
@@ -923,10 +545,8 @@
    *          {@code false} if it was not for some reason (e.g., the
    *          specified operation cannot be cancelled).
    */
-  protected abstract boolean setCancelRequest(CancelRequest
-                                                   cancelRequest);
-
-
+  public abstract boolean setCancelRequest(CancelRequest
+      cancelRequest);
 
   /**
    * Retrieves the cancel request that has been issued for this
@@ -939,8 +559,6 @@
    */
   public abstract CancelRequest getCancelRequest();
 
-
-
   /**
    * Retrieves the cancel result for this operation.
    *
@@ -948,24 +566,14 @@
    *          {@code null} if the operation has not seen and reacted
    *          to a cancel request.
    */
-  public final CancelResult getCancelResult()
-  {
-    return cancelResult;
-  }
-
-
+  public abstract CancelResult getCancelResult();
 
   /**
    * Specifies the cancel result for this operation.
    *
    * @param  cancelResult  The cancel result for this operation.
    */
-  public final void setCancelResult(CancelResult cancelResult)
-  {
-    this.cancelResult = cancelResult;
-  }
-
-
+  public abstract void setCancelResult(CancelResult cancelResult);
 
   /**
    * Indicates that this operation has been cancelled.  If
@@ -976,40 +584,14 @@
    *
    * @param  cancelRequest  The request to cancel this operation.
    */
-  protected final void indicateCancelled(CancelRequest cancelRequest)
-  {
-    setCancelResult(CancelResult.CANCELED);
-
-    if (cancelRequest.notifyOriginalRequestor() ||
-        DirectoryServer.notifyAbandonedOperations())
-    {
-      setResultCode(ResultCode.CANCELED);
-
-      String cancelReason = cancelRequest.getCancelReason();
-      if (cancelReason != null)
-      {
-        appendErrorMessage(cancelReason);
-      }
-
-      clientConnection.sendResponse(this);
-    }
-  }
-
-
+  public abstract void indicateCancelled(CancelRequest cancelRequest);
 
   /**
    * Retrieves a string representation of this operation.
    *
    * @return  A string representation of this operation.
    */
-  public final String toString()
-  {
-    StringBuilder buffer = new StringBuilder();
-    toString(buffer);
-    return buffer.toString();
-  }
-
-
+  public abstract String toString();
 
   /**
    * Appends a string representation of this operation to the provided
@@ -1020,8 +602,6 @@
    */
   public abstract void toString(StringBuilder buffer);
 
-
-
   /**
    * Indicates whether this operation needs to be synchronized to
    * other copies of the data.
@@ -1030,9 +610,28 @@
    *                            synchronized, or
    *          <CODE>false</CODE> if it needs to be synchronized.
    */
-  public boolean dontSynchronize()
-  {
-    return dontSynchronizeFlag;
-  }
+  public abstract boolean dontSynchronize();
+
+  /**
+   * Set the time at which the processing stopped for this operation.
+   * This will actually hold a time immediately before the response
+   * was sent to the client.
+   */
+  public abstract void setProcessingStopTime();
+
+  /**
+   * Set the time at which the processing started for this operation.
+   */
+  public abstract void setProcessingStartTime();
+
+  /**
+   * Set the attachments to the operation.
+   *
+   * @param attachments - Attachments to register within the
+   *                      operation
+   */
+  public abstract void setAttachments(Map<String,
+      Object> attachments);
+
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/LeafWorkflowElement.java b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/LeafWorkflowElement.java
new file mode 100644
index 0000000..098e06d
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/LeafWorkflowElement.java
@@ -0,0 +1,40 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.workflowelement;
+
+
+
+/**
+ * This class gathers all the workflow elements that are encapsulating
+ * physical repositories such as local database, remote LDAP servers,
+ * JDBC repository or LDIF flat file.
+ */
+public abstract class LeafWorkflowElement
+  extends WorkflowElement
+{
+  // Empty at the moment.
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/WorkflowElement.java b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/WorkflowElement.java
new file mode 100644
index 0000000..df604c3
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/WorkflowElement.java
@@ -0,0 +1,81 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.workflowelement;
+
+import org.opends.server.types.Operation;
+
+
+/**
+ * This class defines the super class for all the workflow elements. A workflow
+ * element is a task in a workflow. A workflow element can wrap a physical
+ * repository such as a local backend, a remote LDAP server or a local ldif
+ * file. A workflow element can also be used to route operations. This is the
+ * case for load balancing and distribution. And workflow element can be used
+ * in a virtual environment to transform data (DN and attribute renaming,
+ * attribute value renaming...).
+ */
+public abstract class WorkflowElement
+{
+
+  /**
+   * Indicates whether the workflow element encapsulates a private
+   * local backend.
+   */
+  protected boolean isPrivate = false;
+
+
+  /**
+   * Creates a new instance of the workflow element.
+   */
+  public WorkflowElement()
+  {
+    // No implementation is required.
+  }
+
+
+  /**
+   * Executes the workflow element for an operation.
+   *
+   * @param operation the operation to execute
+   */
+  public abstract void execute(
+      Operation operation
+      );
+
+
+  /**
+   * Indicates whether the workflow element encapsulates a private
+   * local backend.
+   *
+   * @return <code>true</code> if the workflow element encapsulates a private
+   *         local backend, <code>false</code> otherwise
+   */
+  public boolean isPrivate()
+  {
+    return isPrivate;
+  }
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
index d74112b..7e50734 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
@@ -69,13 +69,13 @@
 import org.opends.server.protocols.ldap.BindResponseProtocolOp;
 import org.opends.server.tools.LDAPModify;
 import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
 import org.opends.server.types.FilePermission;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.OperatingSystem;
 import org.opends.server.types.ResultCode;
 
 import static org.testng.Assert.*;
-import static org.testng.Assert.assertEquals;
 
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
@@ -151,7 +151,8 @@
    *                           configuration.
    */
   public static void startServer()
-         throws IOException, InitializationException, ConfigException
+         throws IOException, InitializationException, ConfigException,
+                DirectoryException
   {
     if (SERVER_STARTED)
     {
@@ -352,6 +353,8 @@
     assertTrue(InvocationCounterPlugin.startupCalled());
 
     SERVER_STARTED = true;
+    
+    initializeTestBackend(true);
   }
 
   /**
@@ -439,7 +442,8 @@
    * @throws  Exception  If an unexpected problem occurs.
    */
   public static void initializeTestBackend(boolean createBaseEntry)
-         throws Exception
+         throws IOException, InitializationException, ConfigException,
+                DirectoryException
   {
     startServer();
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java
index ad41d50..fa3e3bf 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java
@@ -35,10 +35,10 @@
 import org.testng.annotations.Test;
 
 import org.opends.server.TestCaseUtils;
-import org.opends.server.admin.std.server.SchemaBackendCfg;
+import org.opends.server.backends.SchemaBackend;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.AddOperation;
-import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.AddOperationBasis;
+import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.core.SchemaConfigManager;
@@ -264,8 +264,8 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    AddOperation addOperation =
-         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, entry.getDN(), entry.getObjectClasses(),
                           entry.getUserAttributes(),
                           entry.getOperationalAttributes());
@@ -287,8 +287,8 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    DeleteOperation deleteOperation =
-         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    DeleteOperationBasis deleteOperation =
+         new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              null, schemaDN);
 
     schemaBackend.deleteEntry(schemaDN, deleteOperation);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
index e511c2a..a409d28 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
@@ -54,6 +54,10 @@
 import static org.testng.Assert.assertEquals;
 import com.sleepycat.je.DatabaseEntry;
 
+import org.opends.server.core.DeleteOperationBasis;
+import org.opends.server.core.ModifyOperationBasis;
+import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation;
+
 /**
  * BackendImpl Tester.
  */
@@ -726,7 +730,7 @@
     InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
 
-    DeleteOperation delete = new DeleteOperation(conn,
+    DeleteOperationBasis delete = new DeleteOperationBasis(conn,
         conn.nextOperationID(),
         conn.nextMessageID(),
         deleteSubTreeControl,
@@ -777,10 +781,10 @@
       entryID = ec.getDN2ID().get(null,
           DN.decode("uid=user.539,ou=People,dc=test,dc=com"));
 
-      DeleteOperation delete = new DeleteOperation(conn,
-          conn.nextOperationID(),
-          conn.nextMessageID(),
-          noControls,
+      DeleteOperationBasis delete = new DeleteOperationBasis(conn,
+        conn.nextOperationID(),
+        conn.nextMessageID(),
+        noControls,
 
           DN.decode("uid=user.539,ou=People,dc=test,dc=com"));
 
@@ -1025,15 +1029,15 @@
       InternalClientConnection conn =
           InternalClientConnection.getRootConnection();
 
-      ModifyOperation modifyOp = new ModifyOperation(conn,
-          conn.nextOperationID(),
-          conn.nextMessageID(),
-          noControls,
-          DN.decode("uid=user.1,ou=People,dc=test,dc=com"),
-          modifications);
+    ModifyOperationBasis modifyOp = new ModifyOperationBasis(conn,
+        conn.nextOperationID(),
+        conn.nextMessageID(),
+        noControls,
+        DN.decode("uid=user.1,ou=People,dc=test,dc=com"),
+        modifications);
 
 
-      backend.replaceEntry(newEntry, modifyOp);
+    backend.replaceEntry(newEntry, modifyOp);
 
       entry = ec.getEntry(DN.decode("uid=user.1,ou=People,dc=test,dc=com"));
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ServerSideSortControlTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ServerSideSortControlTestCase.java
index 82da9bc..388750e 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ServerSideSortControlTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ServerSideSortControlTestCase.java
@@ -28,14 +28,13 @@
 
 
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
 
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.opends.server.TestCaseUtils;
 import org.opends.server.core.DirectoryServer;
@@ -43,19 +42,16 @@
 import org.opends.server.protocols.internal.InternalSearchOperation;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.Control;
-import org.opends.server.types.DereferencePolicy;
-import org.opends.server.types.DirectoryException;
 import org.opends.server.types.DN;
+import org.opends.server.types.DereferencePolicy;
 import org.opends.server.types.Entry;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchScope;
 import org.opends.server.types.SortKey;
 import org.opends.server.types.SortOrder;
-
-import static org.testng.Assert.*;
-
-import static org.opends.server.util.ServerConstants.*;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
 
 
 
@@ -327,6 +323,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -389,6 +386,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -449,6 +447,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -511,6 +510,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -571,6 +571,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -631,6 +632,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -691,6 +693,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertFalse(internalSearch.getResultCode() == ResultCode.SUCCESS);
   }
@@ -723,6 +726,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertFalse(internalSearch.getResultCode() == ResultCode.SUCCESS);
   }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java
index 9b63163..abe7e45 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java
@@ -28,14 +28,15 @@
 
 
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
+import static org.opends.server.util.ServerConstants.OID_SERVER_SIDE_SORT_RESPONSE_CONTROL;
+import static org.opends.server.util.ServerConstants.OID_VLV_RESPONSE_CONTROL;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.fail;
 
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.opends.server.TestCaseUtils;
 import org.opends.server.core.DirectoryServer;
@@ -45,19 +46,14 @@
 import org.opends.server.protocols.ldap.LDAPResultCode;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.Control;
-import org.opends.server.types.DereferencePolicy;
-import org.opends.server.types.DirectoryException;
 import org.opends.server.types.DN;
+import org.opends.server.types.DereferencePolicy;
 import org.opends.server.types.Entry;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchScope;
-import org.opends.server.types.SortKey;
-import org.opends.server.types.SortOrder;
-
-import static org.testng.Assert.*;
-
-import static org.opends.server.util.ServerConstants.*;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
 
 
 
@@ -497,6 +493,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -574,6 +571,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -650,6 +648,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
 
     // It will be successful because it's not a critical control.
@@ -700,6 +699,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
 
     // It will be successful because it's not a critical control.
@@ -751,6 +751,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -827,6 +828,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -905,6 +907,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -983,6 +986,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -1061,6 +1065,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
     assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
 
@@ -1139,6 +1144,7 @@
                   DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                   SearchFilter.createFilterFromString("(objectClass=person)"),
                   null, null);
+
     internalSearch.run();
 
     // It will be successful because the control isn't critical.
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AbandonOperationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AbandonOperationTestCase.java
index 55fc9b7..efa7e1f 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AbandonOperationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AbandonOperationTestCase.java
@@ -28,21 +28,22 @@
 
 
 
+import static org.opends.server.util.ServerConstants.OID_WHO_AM_I_REQUEST;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
 import java.net.Socket;
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
 
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
 import org.opends.server.TestCaseUtils;
 import org.opends.server.plugins.DelayPreOpPlugin;
 import org.opends.server.plugins.DisconnectClientPlugin;
 import org.opends.server.plugins.InvocationCounterPlugin;
-import org.opends.server.protocols.asn1.ASN1Element;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.asn1.ASN1Reader;
-import org.opends.server.protocols.asn1.ASN1Sequence;
 import org.opends.server.protocols.asn1.ASN1Writer;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.ldap.AbandonRequestProtocolOp;
@@ -61,10 +62,10 @@
 import org.opends.server.protocols.ldap.LDAPMessage;
 import org.opends.server.protocols.ldap.LDAPModification;
 import org.opends.server.protocols.ldap.LDAPResultCode;
-import org.opends.server.protocols.ldap.ModifyRequestProtocolOp;
-import org.opends.server.protocols.ldap.ModifyResponseProtocolOp;
 import org.opends.server.protocols.ldap.ModifyDNRequestProtocolOp;
 import org.opends.server.protocols.ldap.ModifyDNResponseProtocolOp;
+import org.opends.server.protocols.ldap.ModifyRequestProtocolOp;
+import org.opends.server.protocols.ldap.ModifyResponseProtocolOp;
 import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
 import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
 import org.opends.server.types.CancelRequest;
@@ -78,10 +79,7 @@
 import org.opends.server.types.RawModification;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchScope;
-
-import static org.testng.Assert.*;
-
-import static org.opends.server.util.ServerConstants.*;
+import org.testng.annotations.Test;
 
 
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AddOperationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AddOperationTestCase.java
index 770ee7d..b7e6bef 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AddOperationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/AddOperationTestCase.java
@@ -128,17 +128,17 @@
 
     Operation[] opArray = new Operation[]
     {
-      new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                        null, new ASN1OctetString("ou=People,o=test"),
                        ldapAttrList),
-      new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                        noControls, new ASN1OctetString("ou=People,o=test"),
                        ldapAttrList),
-      new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                        null, entry.getDN(), entry.getObjectClasses(),
                        entry.getUserAttributes(),
                        entry.getOperationalAttributes()),
-      new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                        noControls, entry.getDN(), entry.getObjectClasses(),
                        entry.getUserAttributes(),
                        entry.getOperationalAttributes()),
@@ -182,7 +182,7 @@
    * @param  addOperation  The add operation to be tested.
    */
   @Test(dataProvider = "addOperations")
-  public void testGetAndSetRawEntryDN(AddOperation addOperation)
+  public void testGetAndSetRawEntryDN(AddOperationBasis addOperation)
   {
     ByteString originalDN = addOperation.getRawEntryDN();
     assertNotNull(originalDN);
@@ -201,7 +201,7 @@
 
   /**
    * Tests the <CODE>getEntryDN</CODE> method for the case in which we expect
-   * the DN to be initially null.
+   * the rawEntryDN to be decoded.
    */
   @Test()
   public void testGetEntryDNInitiallyNull()
@@ -220,11 +220,11 @@
     values.add(new ASN1OctetString("People"));
     ldapAttrList.add(new LDAPAttribute("ou", values));
 
-    AddOperation addOperation =
-         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, new ASN1OctetString("ou=People,o=test"),
                           ldapAttrList);
-    assertNull(addOperation.getEntryDN());
+    assertNotNull(addOperation.getEntryDN());
   }
 
 
@@ -248,8 +248,8 @@
          "objectClass: organizationalUnit",
          "ou: People");
 
-    AddOperation addOperation =
-         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, entry.getDN(), entry.getObjectClasses(),
                           entry.getUserAttributes(),
                           entry.getOperationalAttributes());
@@ -261,7 +261,7 @@
   /**
    * Tests the <CODE>getEntryDN</CODE> method for the case in which we expect
    * the DN to be initially non-null but then becomes null after the raw DN is
-   * changed.
+   * changed, and <CODE>getEntryDN</CODE> method recomputes it again
    *
    * @throws  Exception  If an unexpected problem occurs.
    */
@@ -278,15 +278,15 @@
          "objectClass: organizationalUnit",
          "ou: People");
 
-    AddOperation addOperation =
-         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, entry.getDN(), entry.getObjectClasses(),
                           entry.getUserAttributes(),
                           entry.getOperationalAttributes());
     assertNotNull(addOperation.getEntryDN());
 
     addOperation.setRawEntryDN(new ASN1OctetString("ou=Users,o=test"));
-    assertNull(addOperation.getEntryDN());
+    assertNotNull(addOperation.getEntryDN());
   }
 
 
@@ -298,7 +298,7 @@
    * @param  addOperation  The add operation to be tested.
    */
   @Test(dataProvider = "addOperations")
-  public void testGetAndSetRawAttributes(AddOperation addOperation)
+  public void testGetAndSetRawAttributes(AddOperationBasis addOperation)
   {
     List<RawAttribute> rawAttrs = addOperation.getRawAttributes();
     assertNotNull(rawAttrs);
@@ -579,7 +579,6 @@
    */
   private void retrieveCompletedOperationElements(AddOperation addOperation)
   {
-    assertNotNull(addOperation.getEntryToAdd());
     assertTrue(addOperation.getProcessingStartTime() > 0);
     assertTrue(addOperation.getProcessingStopTime() >=
                addOperation.getProcessingStartTime());
@@ -1984,8 +1983,8 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    AddOperation addOperation =
-         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, entry.getDN(), entry.getObjectClasses(),
                           entry.getUserAttributes(),
                           entry.getOperationalAttributes());
@@ -2517,8 +2516,8 @@
     rawAttrs.add(RawAttribute.create("objectClass", ocValues));
     rawAttrs.add(RawAttribute.create("o", "test"));
 
-    AddOperation addOperation =
-         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           controls, new ASN1OctetString("o=test"), rawAttrs);
     addOperation.run();
     assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
index 2c0d57f..b9a23ba 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
@@ -37,9 +37,7 @@
 import org.testng.annotations.BeforeMethod;
 
 import org.opends.server.TestCaseUtils;
-import org.opends.server.core.AddOperation;
 import org.opends.server.core.BindOperation;
-import org.opends.server.core.ModifyOperation;
 import org.opends.server.plugins.DisconnectClientPlugin;
 import org.opends.server.plugins.InvocationCounterPlugin;
 import org.opends.server.plugins.ShortCircuitPlugin;
@@ -97,45 +95,45 @@
 
     BindOperation[] simpleBinds = new BindOperation[]
     {
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", new ASN1OctetString(),
                         new ASN1OctetString()),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", new ASN1OctetString(),
                         new ASN1OctetString()),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", nullOS, new ASN1OctetString()),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", nullOS, new ASN1OctetString()),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", new ASN1OctetString(), nullOS),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", new ASN1OctetString(), nullOS),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", nullOS, nullOS),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", nullOS, nullOS),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3",
                         new ASN1OctetString("cn=Directory Manager"),
                         new ASN1OctetString("password")),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", DN.nullDN(), new ASN1OctetString()),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", DN.nullDN(), new ASN1OctetString()),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", nullDN, new ASN1OctetString()),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", nullDN, new ASN1OctetString()),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", DN.nullDN(), nullOS),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", DN.nullDN(), nullOS),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", nullDN, nullOS),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", nullDN, nullOS),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", DN.decode("cn=Directory Manager"),
                         new ASN1OctetString("password"))
     };
@@ -170,45 +168,45 @@
 
     BindOperation[] saslBinds = new BindOperation[]
     {
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", new ASN1OctetString(), "EXTERNAL", null),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", new ASN1OctetString(), "EXTERNAL",
                         null),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", nullOS, "EXTERNAL", null),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", nullOS, "EXTERNAL", null),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", new ASN1OctetString(), "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", new ASN1OctetString(), "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", nullOS, "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", nullOS, "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", DN.nullDN(), "EXTERNAL", null),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", DN.nullDN(), "EXTERNAL", null),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", nullDN, "EXTERNAL", null),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", nullDN, "EXTERNAL", null),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", DN.nullDN(), "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", DN.nullDN(), "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         null, "3", nullDN, "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password")),
-      new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                         noControls, "3", nullDN, "PLAIN",
                         new ASN1OctetString("\u0000u:test.user\u0000password"))
     };
@@ -1670,8 +1668,8 @@
     ArrayList<Control> requestControls = new ArrayList<Control>(1);
     requestControls.add(new Control("1.2.3.4", true));
 
-    BindOperation bindOperation =
-         new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                            requestControls, "3", DN.nullDN(),
                         new ASN1OctetString());
     bindOperation.run();
@@ -1697,8 +1695,8 @@
     ASN1OctetString saslCreds =
          new ASN1OctetString("\u0000dn:cn=Directory Manager\u0000password");
 
-    BindOperation bindOperation =
-         new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                            requestControls, "3", DN.nullDN(), "PLAIN",
                         saslCreds);
     bindOperation.run();
@@ -1721,10 +1719,11 @@
     ArrayList<Control> requestControls = new ArrayList<Control>(1);
     requestControls.add(new Control("1.2.3.4", false));
 
-    BindOperation bindOperation =
-         new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                            requestControls, "3", DN.nullDN(),
                            new ASN1OctetString());
+
     bindOperation.run();
     assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
   }
@@ -1747,8 +1746,8 @@
     ASN1OctetString saslCreds =
          new ASN1OctetString("\u0000dn:cn=Directory Manager\u0000password");
 
-    BindOperation bindOperation =
-         new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                            requestControls, "3", DN.nullDN(), "PLAIN",
                            saslCreds);
     bindOperation.run();
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java
index 71426a8..4d9037b 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java
@@ -33,7 +33,6 @@
 import java.util.List;
 import java.util.concurrent.locks.Lock;
 
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 import org.testng.annotations.AfterMethod;
 
@@ -44,13 +43,11 @@
 import org.opends.server.protocols.asn1.ASN1Element;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.asn1.ASN1Reader;
-import org.opends.server.protocols.asn1.ASN1Sequence;
 import org.opends.server.protocols.asn1.ASN1Writer;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.ldap.BindRequestProtocolOp;
 import org.opends.server.protocols.ldap.BindResponseProtocolOp;
 import org.opends.server.protocols.ldap.DeleteRequestProtocolOp;
-import org.opends.server.protocols.ldap.DeleteResponseProtocolOp;
 import org.opends.server.protocols.ldap.LDAPMessage;
 import org.opends.server.tools.LDAPDelete;
 import org.opends.server.types.ByteString;
@@ -63,6 +60,7 @@
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.WritabilityMode;
 import org.opends.server.types.DirectoryException;
+import org.opends.server.workflowelement.localbackend.LocalBackendDeleteOperation;
 
 import static org.testng.Assert.*;
 
@@ -95,22 +93,22 @@
 
     return new Operation[]
     {
-      new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           new ArrayList<Control>(), new ASN1OctetString()),
-      new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, new ASN1OctetString()),
-      new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           new ArrayList<Control>(),
                           new ASN1OctetString("o=test")),
-      new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, new ASN1OctetString("o=test")),
-      new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           new ArrayList<Control>(), DN.nullDN()),
-      new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, DN.nullDN()),
-      new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           new ArrayList<Control>(), DN.decode("o=test")),
-      new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           null, DN.decode("o=test"))
     };
   }
@@ -140,7 +138,8 @@
 
 
   /**
-   * Tests the <CODE>getEntryDN</CODE> method when it should be null.
+   * Tests the <CODE>getEntryDN</CODE> method that should decode the rawEntryDN
+   * to compute the entryDN.
    */
   @Test()
   public void testGetEntryDNNull()
@@ -149,9 +148,9 @@
          InternalClientConnection.getRootConnection();
 
     DeleteOperation deleteOperation =
-         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+         new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              null, new ASN1OctetString("o=test"));
-    assertNull(deleteOperation.getEntryDN());
+    assertNotNull(deleteOperation.getEntryDN());
   }
 
 
@@ -169,7 +168,7 @@
          InternalClientConnection.getRootConnection();
 
     DeleteOperation deleteOperation =
-         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+         new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              null, DN.decode("o=test"));
     assertNotNull(deleteOperation.getEntryDN());
   }
@@ -178,7 +177,10 @@
 
   /**
    * Tests the <CODE>getEntryDN</CODE> method when it originally started as
-   * non-null but then was changed to null.
+   * non-null, then was changed to null; because of the call to the
+   * <CODE>setRawEntry<CODE> method, and becomes non-null again because
+   * of the call to the <CODE>getEntryDN</CODE> again.
+   * 
    *
    * @throws  Exception  If an unexpected problem occurs.
    */
@@ -190,12 +192,12 @@
          InternalClientConnection.getRootConnection();
 
     DeleteOperation deleteOperation =
-         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+         new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              null, DN.decode("o=test"));
     assertNotNull(deleteOperation.getEntryDN());
 
     deleteOperation.setRawEntryDN(new ASN1OctetString("dc=example,dc=com"));
-    assertNull(deleteOperation.getEntryDN());
+    assertNotNull(deleteOperation.getEntryDN());
   }
 
 
@@ -208,7 +210,6 @@
                     DeleteOperation deleteOperation)
          throws Exception
   {
-    assertNotNull(deleteOperation.getEntryToDelete());
     assertTrue(deleteOperation.getProcessingStartTime() > 0);
     assertTrue(deleteOperation.getProcessingStopTime() >=
                deleteOperation.getProcessingStartTime());
@@ -241,8 +242,13 @@
          conn.processDelete(new ASN1OctetString("o=test"));
     assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
     retrieveCompletedOperationElements(deleteOperation);
-
-    assertNotNull(deleteOperation.getEntryToDelete());
+    List localOps =
+      (List) (deleteOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS));
+    assertNotNull(localOps);
+    for (Object localOp : localOps){
+      LocalBackendDeleteOperation curOp = (LocalBackendDeleteOperation) localOp;
+      assertNotNull(curOp.getEntryToDelete());
+    }
   }
 
 
@@ -265,7 +271,13 @@
     DeleteOperation deleteOperation =
          conn.processDelete(new ASN1OctetString("ou=People,o=test"));
     assertFalse(deleteOperation.getResultCode() == ResultCode.SUCCESS);
-    assertNull(deleteOperation.getEntryToDelete());
+    List localOps =
+      (List) (deleteOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS));
+    assertNotNull(localOps);
+    for (Object localOp : localOps){
+      LocalBackendDeleteOperation curOp = (LocalBackendDeleteOperation) localOp;
+      assertNull(curOp.getEntryToDelete());
+    }
   }
 
 
@@ -795,8 +807,8 @@
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
 
-    DeleteOperation deleteOperation =
-         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    DeleteOperationBasis deleteOperation =
+         new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              null, new ASN1OctetString("o=test"));
 
     CancelRequest cancelRequest = new CancelRequest(false,
@@ -1152,8 +1164,8 @@
     List<Control> controls =
          ShortCircuitPlugin.createShortCircuitControlList(0, "PreParse");
 
-    DeleteOperation deleteOperation =
-         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    DeleteOperationBasis deleteOperation =
+         new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              controls, new ASN1OctetString("o=test"));
     deleteOperation.run();
     assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
index 61be52b..5fb6a1c 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
@@ -31,6 +31,7 @@
 import java.net.Socket;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.locks.Lock;
 
 import org.testng.annotations.DataProvider;
@@ -58,9 +59,9 @@
 import org.opends.server.protocols.ldap.LDAPFilter;
 import org.opends.server.tools.LDAPModify;
 import org.opends.server.types.*;
+import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation;
 
 import static org.testng.Assert.*;
-import static org.testng.Assert.assertEquals;
 
 import static org.opends.server.protocols.ldap.LDAPConstants.*;
 
@@ -109,48 +110,48 @@
     LDAPAttribute ldapAttr = new LDAPAttribute("description", ldapValues);
     ldapMods.add(new LDAPModification(ModificationType.ADD, ldapAttr));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString("o=test"), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString("o=test"), ldapMods));
 
     ldapMods = new ArrayList<RawModification>();
     ldapMods.add(new LDAPModification(ModificationType.DELETE, ldapAttr));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString("o=test"), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString("o=test"), ldapMods));
 
     ldapMods = new ArrayList<RawModification>();
     ldapMods.add(new LDAPModification(ModificationType.REPLACE, ldapAttr));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString("o=test"), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString("o=test"), ldapMods));
 
@@ -161,16 +162,16 @@
     ldapMods.add(new LDAPModification(ModificationType.DELETE, ldapAttr));
     ldapMods.add(new LDAPModification(ModificationType.ADD, ldapAttr2));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString("o=test"), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString("o=test"), ldapMods));
 
@@ -179,16 +180,16 @@
     ldapMods.add(new LDAPModification(ModificationType.REPLACE, ldapAttr));
     ldapMods.add(new LDAPModification(ModificationType.REPLACE, ldapAttr2));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString(), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    new ASN1OctetString("o=test"), ldapMods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    new ASN1OctetString("o=test"), ldapMods));
 
@@ -198,15 +199,15 @@
     mods.add(new Modification(ModificationType.ADD,
                               new Attribute("description", "foo")));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null, DN.nullDN(), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    DN.decode("o=test"), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    DN.decode("o=test"), mods));
 
@@ -214,15 +215,15 @@
     mods.add(new Modification(ModificationType.DELETE,
                               new Attribute("description", "foo")));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null, DN.nullDN(), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    DN.decode("o=test"), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    DN.decode("o=test"), mods));
 
@@ -230,15 +231,15 @@
     mods.add(new Modification(ModificationType.REPLACE,
                               new Attribute("description", "foo")));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null, DN.nullDN(), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    DN.decode("o=test"), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    DN.decode("o=test"), mods));
 
@@ -248,15 +249,15 @@
     mods.add(new Modification(ModificationType.ADD,
                               new Attribute("description", "bar")));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null, DN.nullDN(), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    DN.decode("o=test"), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    DN.decode("o=test"), mods));
 
@@ -266,15 +267,15 @@
     mods.add(new Modification(ModificationType.REPLACE,
                               new Attribute("cn", "bar")));
 
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null, DN.nullDN(), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls, DN.nullDN(),
                                    mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), null,
                                    DN.decode("o=test"), mods));
-    opList.add(new ModifyOperation(conn, conn.nextOperationID(),
+    opList.add(new ModifyOperationBasis(conn, conn.nextOperationID(),
                                    conn.nextMessageID(), noControls,
                                    DN.decode("o=test"), mods));
 
@@ -349,8 +350,8 @@
 
 
   /**
-   * Tests the <CODE>getEntryDN</CODE> method for the case in which we expect
-   * the DN to be initially null.
+   * Tests the <CODE>getEntryDN</CODE> method that should decode
+   * the raw entry dn and return a non-null DN.
    */
   @Test()
   public void testGetEntryDNInitiallyNull()
@@ -366,9 +367,9 @@
     mods.add(new LDAPModification(ModificationType.REPLACE, attr));
 
     ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              null, new ASN1OctetString(), mods);
-    assertNull(modifyOperation.getEntryDN());
+    assertNotNull(modifyOperation.getEntryDN());
   }
 
 
@@ -390,7 +391,7 @@
     mods.add(new Modification(ModificationType.REPLACE,
                               new Attribute("description", "foo")));
     ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              null, DN.nullDN(), mods);
     assertNotNull(modifyOperation.getEntryDN());
   }
@@ -399,8 +400,8 @@
 
   /**
    * Tests the <CODE>getEntryDN</CODE> method for the case in which we expect
-   * the DN to be initially non-null but then becomes null after the raw DN is
-   * changed.
+   * the DN to be initially non-null, then is null after the raw DN is
+   * changed, but becomes non-null after the call to <CODE>getEntryDN</CODE>.
    *
    * @throws  Exception  If an unexpected problem occurs.
    */
@@ -415,12 +416,12 @@
     mods.add(new Modification(ModificationType.REPLACE,
                               new Attribute("description", "foo")));
     ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              null, DN.nullDN(), mods);
     assertNotNull(modifyOperation.getEntryDN());
 
     modifyOperation.setRawEntryDN(new ASN1OctetString("ou=Users,o=test"));
-    assertNull(modifyOperation.getEntryDN());
+    assertNotNull(modifyOperation.getEntryDN());
   }
 
 
@@ -468,16 +469,22 @@
   private void retrieveSuccessfulOperationElements(
                     ModifyOperation modifyOperation)
   {
-    assertNotNull(modifyOperation.getCurrentEntry());
-    assertNotNull(modifyOperation.getModifiedEntry());
     assertTrue(modifyOperation.getProcessingStartTime() > 0);
     assertTrue(modifyOperation.getProcessingStopTime() >=
                modifyOperation.getProcessingStartTime());
     assertTrue(modifyOperation.getProcessingTime() >= 0);
     assertNotNull(modifyOperation.getResponseLogElements());
 
-    modifyOperation.getNewPasswords();
-    modifyOperation.getCurrentPasswords();
+    List localOps =
+      (List) (modifyOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS));
+    assertNotNull(localOps);
+    for (Object localOp : localOps){
+      LocalBackendModifyOperation curOp = (LocalBackendModifyOperation) localOp;
+      curOp.getNewPasswords();
+      curOp.getCurrentPasswords();
+      assertNotNull(curOp.getCurrentEntry());
+      assertNotNull(curOp.getModifiedEntry());
+    }
 
     long changeNumber = modifyOperation.getChangeNumber();
     modifyOperation.setChangeNumber(changeNumber);
@@ -501,11 +508,6 @@
     assertTrue(modifyOperation.getProcessingTime() >= 0);
     assertNotNull(modifyOperation.getResponseLogElements());
 
-    modifyOperation.getCurrentEntry();
-    modifyOperation.getModifiedEntry();
-    modifyOperation.getNewPasswords();
-    modifyOperation.getCurrentPasswords();
-
     long changeNumber = modifyOperation.getChangeNumber();
     modifyOperation.setChangeNumber(changeNumber);
   }
@@ -2014,7 +2016,7 @@
               InternalClientConnection.nextOperationID(),
               InternalClientConnection.nextMessageID(),
               new ArrayList<Control>(),
-              new ASN1OctetString(baseDN),
+              new ASN1OctetString("uid=test.user," + baseDN),
               SearchScope.WHOLE_SUBTREE,
               DereferencePolicy.NEVER_DEREF_ALIASES,
               Integer.MAX_VALUE,
@@ -4075,8 +4077,8 @@
     ArrayList<RawModification> mods = new ArrayList<RawModification>();
     mods.add(new LDAPModification(ModificationType.REPLACE, attr));
 
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              null, new ASN1OctetString(baseDN), mods);
 
     CancelRequest cancelRequest = new CancelRequest(false,
@@ -4528,8 +4530,8 @@
     mods.add(RawModification.create(ModificationType.REPLACE, "description",
                                     "foo"));
 
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              controls, new ASN1OctetString("o=test"), mods);
     modifyOperation.run();
     assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/NetworkGroupTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/NetworkGroupTest.java
new file mode 100644
index 0000000..9819e76
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/NetworkGroupTest.java
@@ -0,0 +1,470 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.workflowelement.WorkflowElement;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+/**
+ * This set of tests checks that network groups are properly created.
+ */
+public class NetworkGroupTest
+{
+  //===========================================================================
+  //
+  //                      B E F O R E    C L A S S
+  //
+  //===========================================================================
+
+  /**
+   * Sets up the environment for performing the tests in this suite.
+   *
+   * @throws Exception if the environment could not be set up.
+   */
+  @BeforeClass
+  public void setUp()
+    throws Exception
+  {
+    // This test suite depends on having the schema available,
+    // so we'll start the server.
+    TestCaseUtils.startServer();
+  }
+
+  //===========================================================================
+  //
+  //                      D A T A    P R O V I D E R
+  //
+  //===========================================================================
+
+  /**
+   * Provides a single DN to search a workflow in a network group.
+   * 
+   * Each set of DNs is composed of:
+   * - one baseDN
+   * - one subordinateDN
+   * - a boolean telling whether we expect to find a workflow for the baseDN
+   *
+   * @return set of DNs
+   * @throws Exception  when DN.decode fails
+   */
+  @DataProvider(name = "DNSet_1")
+  public Object[][] initDNSet_1()
+    throws Exception
+  {
+    DN dnRootDSE = null;
+    DN dnConfig  = null;
+    DN dnMonitor = null;
+    DN dnSchema  = null;
+    DN dnTasks   = null;
+    DN dnBackups = null;
+    DN dnDummy   = null;
+
+    DN dnSubordinateConfig  = null;
+    DN dnSubordinateMonitor = null;
+    DN dnSubordinateTasks   = null;
+
+    try
+    {
+      dnRootDSE = DN.decode("");
+      dnConfig  = DN.decode("cn=config");
+      dnMonitor = DN.decode("cn=monitor");
+      dnSchema  = DN.decode("cn=schema");
+      dnTasks   = DN.decode("cn=tasks");
+      dnBackups = DN.decode("cn=backups");
+      dnDummy   = DN.decode("o=dummy_suffix");
+
+      dnSubordinateConfig  = DN.decode("cn=Work Queue,cn=config");
+      dnSubordinateMonitor = DN.decode("cn=schema Backend,cn=monitor");
+      dnSubordinateTasks   = DN.decode("cn=Scheduled Tasks,cn=tasks");
+
+      // No DN subordinate for schema because the schema backend is
+      // currently empty.
+      // No DN subordinate for cn=backups because by default there is no
+      // child entry under cn=backups.
+    }
+    catch (DirectoryException de)
+    {
+      throw de;
+    }
+
+    // Sets of DNs
+    Object[][] myData =
+    {
+        { dnRootDSE,  null,                 true  },
+        { dnConfig,   dnSubordinateConfig,  true  },
+        { dnMonitor,  dnSubordinateMonitor, true  },
+        { dnTasks,    dnSubordinateTasks,   true  },
+        { dnSchema,   null,                 true  },
+        { dnBackups,  null,                 true  },
+        { dnDummy,    null,                 false },
+    };
+
+    return myData;
+  }
+
+  /**
+   * Provides information to create a network group.
+   *
+   * Each set of DNs contains:
+   * - one base DN for the 1st workflow
+   * - one base DN for the 2nd workflow
+   * - one base DN for the 3rd workflow
+   * - one subordinate DN for the 1st workflow
+   * - one subordinate DN for the 2nd workflow
+   * - one subordinate DN for the 3rd workflow
+   * - one unrelated DN which has no hierarchical relationship with
+   *   any of the above DNs
+
+   */
+  @DataProvider (name = "DNSet_2")
+  public Object[][] initDNSet_2()
+    throws Exception
+  {
+    // Network group definition
+    DN     dn1          = null;
+    DN     dn2          = null;
+    DN     dn3          = null;
+    DN     subordinate1 = null;
+    DN     subordinate2 = null;
+    DN     subordinate3 = null;
+    DN     unrelatedDN  = null;
+    try
+    {
+      dn1          = DN.decode("o=test1");
+      dn2          = DN.decode("o=test2");
+      dn3          = DN.decode("o=test3");
+      subordinate1 = DN.decode("ou=subtest1,o=test1");
+      subordinate2 = DN.decode("ou=subtest2,o=test2");
+      subordinate3 = DN.decode("ou=subtest3,o=test3");
+      unrelatedDN  = DN.decode("o=dummy");
+    }
+    catch (DirectoryException de)
+    {
+      throw de;
+    }
+
+    // Network group info
+    Object[][] myData =
+    {
+        // Test1: one DN for one workflow
+        {
+          dn1,
+          null,
+          null,
+          subordinate1,
+          null,
+          null,
+          unrelatedDN
+        },
+        // Test2: two DNs for two workflows
+        {
+          dn1,
+          dn2,
+          null,
+          subordinate1,
+          subordinate2,
+          null,
+          unrelatedDN
+        },
+        // Test3: three DNs for three workflows
+        {
+          dn1,
+          dn2,
+          dn3,
+          subordinate1,
+          subordinate2,
+          subordinate3,
+          unrelatedDN
+        }
+    };
+
+    return myData;
+  }
+
+
+  //===========================================================================
+  //
+  //                        T E S T   C A S E S
+  //
+  //===========================================================================
+
+  /**
+   * Gets a workflow candidate in the default network group.
+   *
+   *  @param dnToSearch     the DN of a workflow in the default network group
+   *  @param dnSubordinate  a subordinate of dnToSearch
+   *  @param exists         true if we are supposed to find a workflow for
+   *                        dnToSearch
+   */
+  @Test (dataProvider = "DNSet_1", groups = "virtual")
+  public void checkDefaultNetworkGroup(
+      DN      dnToSearch,
+      DN      dnSubordinate,
+      boolean exists
+      )
+  {
+    // let's get the default network group (it should always exist)
+    NetworkGroup defaultNG = NetworkGroup.getDefaultNetworkGroup();
+    assertNotNull(defaultNG);
+
+    // let's check the routing through the network group
+    doCheckNetworkGroup(defaultNG, dnToSearch, dnSubordinate, exists);
+
+    // Dump info
+    StringBuffer sb = defaultNG.toString("defaultNetworkGroup> ");
+    writeln(sb.toString());
+  }
+
+
+  /**
+   * Checks the DN routing through a network group.
+   *
+   * @param networkGroup    the network group to use for the check
+   * @param dnToSearch      the DN of a workflow in the network group
+   * @param dnSubordinate   a subordinate of dnToSearch
+   * @param shouldExist     true if we are supposed to find a workflow for
+   *                        dnToSearch
+   */
+  private void doCheckNetworkGroup(
+      NetworkGroup networkGroup,
+      DN           dnToSearch,
+      DN           dnSubordinate,
+      boolean      shouldExist
+      )
+  {
+    // Let's retrieve the workflow that maps best the dnToSearch
+    Workflow workflow = networkGroup.getWorkflowCandidate(dnToSearch);
+    if (shouldExist)
+    {
+      assertNotNull(workflow);
+    }
+    else
+    {
+      assertNull(workflow);
+    }
+
+    // let's retrieve the workflow that handles the DN subordinate:
+    // it should be the same than the one for dnToSearch
+    if (dnSubordinate != null)
+    {
+       Workflow workflow2 = networkGroup.getWorkflowCandidate(dnSubordinate);
+       assertEquals(workflow2, workflow);
+    }
+  }
+
+
+  /**
+   * Creates a network group with several workflows inside. Routing operation
+   * is done through the default root DSE workflow (which is automatically
+   * created by the network group class).
+   *
+   * @param dn1           the DN for the 1st workflow
+   * @param dn2           the DN for the 2nd workflow
+   * @param dn3           the DN for the 3rd workflow
+   * @param subordinate1  the subordinate DN for the 1st workflow
+   * @param subordinate2  the subordinate DN for the 2nd workflow
+   * @param subordinate3  the subordinate DN for the 3rd workflow
+   * @param unrelatedDN   a DN with no hierarchical relationship with
+   *                      any of the DNs above
+   */
+  @Test (dataProvider = "DNSet_2", groups = "virtual")
+  public void createNetworkGroup2(
+      DN dn1,
+      DN dn2,
+      DN dn3,
+      DN subordinate1,
+      DN subordinate2,
+      DN subordinate3,
+      DN unrelatedDN
+      )
+  {
+    String networkGroupName = "Network Group for test2";
+
+    // Create the network group
+    NetworkGroup ng = new NetworkGroup(networkGroupName);
+    assertNotNull(ng);
+
+    // Register the network group
+    NetworkGroup.registerNetworkGroup(ng);
+
+    // Create and register workflow 1
+    if (dn1 != null)
+    {
+      createAndRegisterWorkflow(ng, dn1, subordinate1, unrelatedDN);
+    }
+
+    // Create and register workflow 2
+    if (dn2 != null)
+    {
+      createAndRegisterWorkflow(ng, dn2, subordinate2, unrelatedDN);
+    }
+
+    // Create and register workflow 3
+    if (dn3 != null)
+    {
+      createAndRegisterWorkflow(ng, dn3, subordinate3, unrelatedDN);
+    }
+
+    // Dump info
+    StringBuffer sb = ng.toString("createNetworkGroup2(" + dn1 + ")> ");
+    writeln(sb.toString());
+
+    // Dump info of defaultNetworkGroup
+    StringBuffer sb2 =
+       NetworkGroup.getDefaultNetworkGroup().toString("defaultNetworkGroup> ");
+    writeln(sb2.toString());
+    
+    // Deregister the workflow1...
+    deregisterWorkflow(ng, dn1, subordinate1, unrelatedDN);
+    
+    // ... and dump info again
+    sb = ng.toString("DEREGISTER Workflow> ");
+    writeln(sb.toString());
+
+    // dump info of defaultNetworkGroup
+    sb2 = NetworkGroup.getDefaultNetworkGroup().toString(
+        "DEREGISTER defaultNetworkGroup> "
+        );
+    writeln(sb2.toString());
+  }
+
+
+  /**
+   * Creates a workflow and register it with a network group.
+   *
+   * @param networkGroup     a network group to register the workflow with
+   * @param workflowBaseDN   the base DN of the workflow to register
+   * @param subordinateDN    subordinate DN of the workflowBaseDN
+   * @param unrelatedDN      a DN with no hierarchical relationship with
+   *                         any of the base DN above
+   */
+  private void createAndRegisterWorkflow(
+      NetworkGroup networkGroup,
+      DN           workflowBaseDN,
+      DN           subordinateDN,
+      DN           unrelatedDN
+      )
+  {
+    assertNotNull(networkGroup);
+
+    // Create and register a workflow (no task in the workflow)
+    WorkflowElement rootWE = null;
+    WorkflowImpl realWorkflow = new WorkflowImpl(workflowBaseDN, rootWE);
+    assertNotNull(realWorkflow);
+
+    // Register the workflow with the network group.
+    networkGroup.registerWorkflow(realWorkflow);
+
+    // Now check that workflow is accessible through the network group
+    Workflow electedWorkflow;
+    electedWorkflow = networkGroup.getWorkflowCandidate(workflowBaseDN);
+    assertEquals(electedWorkflow.getBaseDN(), workflowBaseDN);
+
+    electedWorkflow = networkGroup.getWorkflowCandidate(subordinateDN);
+    assertEquals(electedWorkflow.getBaseDN(), workflowBaseDN);
+
+    // Check that the unrelatedDN is not handled by the workflow
+    Workflow unrelatedWorkflow =
+      networkGroup.getWorkflowCandidate(unrelatedDN);
+    assertNull(unrelatedWorkflow);
+  }
+  
+  
+  /**
+   * Deregisters a workflow with a network group. The workflow to
+   * deregister is identified by its baseDN.
+   *
+   * @param networkGroup     a network group that contains the workflow
+   *                         to deregister
+   * @param workflowBaseDN   the base DN of the workflow to deregister
+   * @param subordinateDN    subordinate DN of the workflowBaseDN
+   * @param unrelatedDN      a DN with no hierarchical relationship with
+   *                         any of the base DN above
+   */
+  private void deregisterWorkflow(
+      NetworkGroup networkGroup,
+      DN           workflowBaseDN,
+      DN           subordinateDN,
+      DN           unrelatedDN
+      )
+  {
+    assertNotNull(networkGroup);
+
+    // get the workflow in the network group
+    Workflow workflow = networkGroup.getWorkflowCandidate(workflowBaseDN);
+    if (workflow == null)
+    {
+      // found no workflow
+      return;
+    }
+
+    // Deregister the workflow with the network group
+    networkGroup.deregisterWorkflow(workflow.getBaseDN());
+
+    // Check that the workflow is no more accessible through the network
+    // group
+    Workflow electedWorkflow;
+    electedWorkflow = networkGroup.getWorkflowCandidate(workflowBaseDN);
+    assertNull(electedWorkflow);
+    electedWorkflow = networkGroup.getWorkflowCandidate(subordinateDN);
+    assertNull(electedWorkflow);
+  }
+  
+  
+  /**
+   * Prints a text to System.out.
+   */
+  private void write(String msg)
+  {
+    boolean dumpInfo = true;
+
+    if (dumpInfo)
+    {
+      System.out.print(msg);
+    }
+  }
+  
+  
+  /**
+   * Prints a text to System.out.
+   */
+  private void writeln(String msg)
+  {
+    write(msg + "\n");
+  }
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
index ef8101a..9864e15 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
@@ -182,7 +182,7 @@
 
     return new Operation[]
     {
-         new SearchOperation(conn,
+         new SearchOperationBasis(conn,
                              InternalClientConnection.nextOperationID(),
                              InternalClientConnection.nextMessageID(),
                              new ArrayList<Control>(),
@@ -203,7 +203,7 @@
    *
    * @param  searchOperation  The operation to be tested.
    */
-  private void examineCompletedOperation(SearchOperation searchOperation)
+  private void examineCompletedOperation(SearchOperationBasis searchOperation)
   {
     assertTrue(searchOperation.getProcessingStartTime() > 0);
     assertTrue(searchOperation.getProcessingStopTime() > 0);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/WorkflowTopologyTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/WorkflowTopologyTest.java
new file mode 100644
index 0000000..265fe7c
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/WorkflowTopologyTest.java
@@ -0,0 +1,938 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import java.util.ArrayList;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.util.UtilTestCase;
+import org.opends.server.workflowelement.WorkflowElement;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+/**
+ * This set of tests checks that workflow topology is properly created.
+ * Topology is based on DN hierarchical relationship. Once the topolofy
+ * is created, we check that the route operation returns the best workflow
+ * candidate for a given request base DN.
+ */
+public class WorkflowTopologyTest extends UtilTestCase
+{
+  //===========================================================================
+  //
+  //                      B E F O R E    C L A S S
+  //
+  //===========================================================================
+
+  /**
+   * Set up the environment for performing the tests in this suite.
+   *
+   * @throws Exception if the environment could not be set up.
+   */
+  @BeforeClass
+  public void setUp()
+    throws Exception
+  {
+    // This test suite depends on having the schema available,
+    // so we'll start the server.
+    TestCaseUtils.startServer();
+  }
+
+
+  //===========================================================================
+  //
+  //                      D A T A    P R O V I D E R
+  //
+  //===========================================================================
+
+  /**
+   * Provide a set of DNs to create a single workflow. Each set of DNs contains
+   * one baseDN for the new workflow to be created, one subordinateDN and one
+   * unrelatedDN which has no hierarchical relationship with the baseDN.
+   *
+   *           baseDN + subordinateDN + unrelatedDN
+   *
+   * Sample scenario for a test using this set of DNs:
+   * 1) creating a workflow with the baseDN
+   * 2) trying to fetch the workflow using the subordinateDN
+   * 3) checking that the workflow cannot be candidate to route a request
+   *    with the unrelatedDN
+   *
+   * @return set of DNs
+   * @throws Exception  when DN.decode fails
+   */
+  @DataProvider(name = "DNSet_1")
+  public Object[][] initDNSet_1()
+    throws Exception
+  {
+    DN dnNull         = null;
+    DN baseDN1        = null;
+    DN subordinateDN1 = null;
+    DN unrelatedDN    = null;
+
+    try
+    {
+      dnNull         = DN.decode ("");
+      baseDN1        = DN.decode ("o=test");
+      subordinateDN1 = DN.decode ("ou=subtest,o=test");
+      unrelatedDN    = DN.decode ("o=dummy");
+    }
+    catch (DirectoryException de)
+    {
+      throw de;
+    }
+
+    // Sets of DNs
+    Object[][] myData =
+    {
+        // SET 1
+        // baseDN is null suffix. There is no unrelatedDN because any DN
+        // is descendant of the null suffix.
+        {
+        dnNull,
+        subordinateDN1,
+        null
+        },
+
+        // SET 2
+        // One baseDN, one subordinateDN and one unrelatedDN
+        {
+        baseDN1,
+        subordinateDN1,
+        unrelatedDN
+        },
+    };
+
+    return myData;
+  }
+
+
+  /**
+   * Provide a set of DNs to create a topology of 3 workflows, and the 3
+   * workflows are in the same hierarchy of DNs: baseDN1 is the superior
+   * of baseDN2 which is the superior of baseDN3:
+   *
+   *           baseDN1 + subordinateDN1
+   *              |
+   *           baseDN2 + subordinateDN2    +   unrelatedDN
+   *              |
+   *           baseDN3 + subordinateDN3
+   *
+   * Each baseDN has a subordinateDN: the workflow with the baseDN should be
+   * the candidate for a request when request base DN is the subordinateDN.
+   *
+   * There is an unrelatedDN which has no hierarchical relationship with any
+   * of the baseDNs. The unrelatedDN is used to check that none of the
+   * workflow can be candidate for the route when a request is using the
+   * unrelatedDN.
+   *
+   * @return set of DNs
+   * @throws Exception  when DN.decode fails
+   */
+  @DataProvider(name = "DNSet_2")
+  public Object[][] initDNSet_2()
+    throws Exception
+  {
+    DN   unrelatedDN    = null;
+    int  nbElem         = 3;
+    DN[] baseDNs        = new DN[nbElem];
+    DN[] subordinateDNs = new DN[nbElem];
+    DN   rootDSE        = null;
+
+    // Create the topology of DNs:
+    //
+    //    o=dummy            ou=test1 (==> W1)
+    //                          |
+    //                          |
+    //           +--------------+
+    //           |              |
+    //           |              |
+    //    ou=subordinate1   ou=test2 (==> W2)
+    //                          |
+    //                          |
+    //                          +--------------------+
+    //                          |                    |
+    //                          |                    |
+    //                      ou=test3 (==> W3)   ou=subordinate2
+    //                          |
+    //                          |
+    //           +--------------+
+    //           |              |
+    //           |              |
+    //    ou=subordinate3
+    try
+    {
+      String suffix         = "ou=test1";
+      String baseDN1        = suffix;
+      String baseDN2        = "ou=test2,"        + baseDN1;
+      String baseDN3        = "ou=test3,"        + baseDN2;
+      String subordinateDN1 = "ou=subordinate1," + baseDN1;
+      String subordinateDN2 = "ou=subordinate2," + baseDN2;
+      String subordinateDN3 = "ou=subordinate3," + baseDN3;
+
+      int i = 0;
+      baseDNs[i]        = DN.decode (baseDN1);
+      subordinateDNs[i] = DN.decode (subordinateDN1);
+      i++;
+      baseDNs[i]        = DN.decode (baseDN2);
+      subordinateDNs[i] = DN.decode (subordinateDN2);
+      i++;
+      baseDNs[i]        = DN.decode (baseDN3);
+      subordinateDNs[i] = DN.decode (subordinateDN3);
+
+      unrelatedDN = DN.decode ("o=dummy");
+      rootDSE     = DN.decode ("");
+    }
+    catch (DirectoryException de)
+    {
+      throw de;
+    }
+
+    // Sets of DNs
+    Object[][] myData =
+    {
+        // SET 1
+        {
+        baseDNs[0], baseDNs[1], baseDNs[2],
+        subordinateDNs[0], subordinateDNs[1], subordinateDNs[2],
+        unrelatedDN
+        },
+
+        // SET 2
+        // Same than SET 1, but the first baseDN is the null suffix DN.
+        // Hence there is no unrelatedDN as any DN is a subordinate of
+        // the null suffix.
+        {
+        rootDSE, baseDNs[1], baseDNs[2],
+        subordinateDNs[0], subordinateDNs[1], subordinateDNs[2],
+        null
+        }
+    };
+
+    return myData;
+  }
+
+
+  /**
+   * Provide a set of DNs to create the following topology:
+   *
+   *                      [W1]
+   *                    baseDN1
+   *                       |
+   *             +---------+--------+
+   *             |                  |
+   *             |                  |
+   *      subordinateDN1     +------+------+
+   *                         |             |
+   *                        [W2]          [W3]
+   *                      baseDN2        baseDN3
+   *                         |             |
+   *                         |             |
+   *                 subordinateDN2    subordinateDN3
+   *
+   *
+   * @return set of DNs
+   * @throws Exception  when DN.decode fails
+   */
+  @DataProvider(name = "DNSet_3")
+  public Object[][] initDNSet_3()
+    throws Exception
+  {
+    DN   unrelatedDN    = null;
+    int  nbElem         = 3;
+    DN[] baseDNs        = new DN[nbElem];
+    DN[] subordinateDNs = new DN[nbElem];
+    DN   rootDSE        = null;
+
+    // Create the topology of DNs:
+    //
+    //    o=dummy       dc=example,dc=com
+    //                          |
+    //                          |
+    //           +--------------+-----------------+
+    //           |              |                 |
+    //    ou=subordinate1   ou=group          ou=people
+    //                          |                 |
+    //                          |                 |
+    //                   ou=subordinate2   ou=subordinate3
+    try
+    {
+      String suffix         = "dc=example,dc=com";
+      String baseDN1        = suffix;
+      String baseDN2        = "ou=group,"        + baseDN1;
+      String baseDN3        = "ou=people,"       + baseDN1;
+      String subordinateDN1 = "ou=subordinate1," + baseDN1;
+      String subordinateDN2 = "ou=subordinate2," + baseDN2;
+      String subordinateDN3 = "ou=subordinate3," + baseDN3;
+
+      int i = 0;
+      baseDNs[i]        = DN.decode (baseDN1);
+      subordinateDNs[i] = DN.decode (subordinateDN1);
+      i++;
+      baseDNs[i]        = DN.decode (baseDN2);
+      subordinateDNs[i] = DN.decode (subordinateDN2);
+      i++;
+      baseDNs[i]        = DN.decode (baseDN3);
+      subordinateDNs[i] = DN.decode (subordinateDN3);
+
+      unrelatedDN = DN.decode ("o=dummy");
+      rootDSE     = DN.decode ("");
+    }
+    catch (DirectoryException de)
+    {
+      throw de;
+    }
+
+    // Sets of DNs
+    Object[][] myData =
+    {
+        // SET 1
+        //
+        //    o=dummy       dc=example,dc=com
+        //                          |
+        //                          |
+        //           +--------------+-----------------+
+        //           |              |                 |
+        //    ou=subordinate1   ou=group          ou=people
+        //                          |                 |
+        //                          |                 |
+        //                   ou=subordinate2   ou=subordinate3
+        {
+        baseDNs[0],
+        baseDNs[1],
+        baseDNs[2],
+        subordinateDNs[0],
+        subordinateDNs[1],
+        subordinateDNs[2],
+        unrelatedDN
+        },
+
+        // SET 2
+        //
+        // The top baseDN is the null suffix. Hence there is no unrelatedDN
+        // as any DN is a subordinate of the null suffix.
+        //
+        //                         "" (rootDSE)
+        //                          |
+        //                          |
+        //           +--------------+-----------------+
+        //           |              |                 |
+        //    ou=subordinate1   ou=group          ou=people
+        //                          |                 |
+        //                          |                 |
+        //                   ou=subordinate2   ou=subordinate3
+        {
+        rootDSE,
+        baseDNs[1],
+        baseDNs[2],
+        subordinateDNs[0],
+        subordinateDNs[1],
+        subordinateDNs[2],
+        null
+        }
+    };
+
+    return myData;
+  }
+
+
+  /**
+   * Provide a set of result codes to test the elaboration of the global
+   * result code.
+   *
+   * @return set of result codes to test
+   * @throws Exception
+   */
+  @DataProvider(name = "ResultCodes_1")
+  public Object[][] initResultCodes_1()
+  {
+    // Short names...
+    ResultCode rcSuccess      = ResultCode.SUCCESS;
+    ResultCode rcNoSuchObject = ResultCode.NO_SUCH_OBJECT;
+    ResultCode rcReferral     = ResultCode.REFERRAL;
+    ResultCode rcOther        = ResultCode.ALIAS_PROBLEM;
+    ResultCode rcOther2       = ResultCode.AUTHORIZATION_DENIED;
+
+    // Sets of DNs
+    Object[][] myData =
+    {
+        // received        current          expected
+        // result code     result code      result code
+        { rcSuccess,       rcNoSuchObject,  rcSuccess  },
+        { rcReferral,      rcSuccess,       rcSuccess  },
+        { rcSuccess,       rcOther,         rcOther    },
+        { rcNoSuchObject,  rcSuccess,       rcSuccess  },
+        { rcNoSuchObject,  rcReferral,      rcReferral },
+        { rcNoSuchObject,  rcOther,         rcOther    },
+        { rcReferral,      rcSuccess,       rcSuccess  },
+        { rcReferral,      rcReferral,      rcSuccess  },
+        { rcReferral,      rcNoSuchObject,  rcReferral },
+        { rcReferral,      rcOther,         rcOther    },
+        { rcOther,         rcSuccess,       rcOther    },
+        { rcOther,         rcReferral,      rcOther    },
+        { rcOther,         rcNoSuchObject,  rcOther    },
+        { rcOther,         rcOther2,        rcOther2   }
+    };
+
+    return myData;
+  }
+
+
+  //===========================================================================
+  //
+  //                        T E S T   C A S E S
+  //
+  //===========================================================================
+
+  /**
+   * Create a single workflow using a baseDN. There is no workflow element
+   * in the workflow nor in the DIT attached to the workflow. Once the
+   * workflow has been created, we are trying to fetch it using the baseDN
+   * and/or the subordinateDN and/or the unrelatedDN.
+   *
+   * @param baseDN         baseDN of the workflow to create
+   * @param subordinateDN  a subordinate DN of baseDN
+   * @param dummyDN        a DN not registered in any workflow
+   */
+  @Test (dataProvider = "DNSet_1", groups = "virtual")
+  public void createWorkflow_basic (
+      DN baseDN,
+      DN subordinateDN,
+      DN dummyDN
+      )
+  {
+    // create a DIT set with the baseDN (no workflow element in the DIT).
+    WorkflowElement nullWE = null;
+    WorkflowImpl realWorkflow = new WorkflowImpl (baseDN, nullWE);
+
+    // Create a worflow with the dit, no pre/post-workflow element.
+    WorkflowTopologyNode workflow = new WorkflowTopologyNode (realWorkflow, null, null);
+
+    // The base DN in the workflow should match baseDN parameter
+    DN workflowBaseDN = workflow.getBaseDN();
+    assertEquals (workflowBaseDN, baseDN);
+
+    // There should be no parent workflow.
+    WorkflowTopologyNode parent = workflow.getParent();
+    assertEquals (parent, null);
+
+    // The workflow should handle the baseDN and subordinateDN.
+    DN readBaseDN = null;
+    readBaseDN = workflow.getParentBaseDN (baseDN);
+    assertEquals (readBaseDN, baseDN);
+    readBaseDN = workflow.getParentBaseDN (subordinateDN);
+    assertEquals (readBaseDN, baseDN);
+
+    // The workflow should not handle the dummyDN.
+    if (dummyDN != null)
+    {
+      readBaseDN = workflow.getParentBaseDN (dummyDN);
+      assertNull (readBaseDN);
+    }
+
+  } // createWorkflow_basic
+
+
+  /**
+   * Create a topology with 2 workflows. The test case contains creation
+   * of clean topologies as well as bad topologies (same baseDN for the parent
+   * and subordinate, subordinate above parent...).
+   *
+   *                 W1 (baseDN)
+   *                 |
+   *                 |
+   *                 W2 (subordinateDN)
+   *
+   * There is no worklfow element attached to the DITs.
+   *
+   * @param baseDn         base DN for the parent workflow (W1)
+   * @param subordinateDN  base DN for the subordinate workflow (W2)
+   * @param unrelatedDN    base DN with no hierarchical relationship with any
+   *                       of the two baseDNs; parameter may be null
+   */
+  @Test (dataProvider = "DNSet_1", groups = "virtual")
+  public void createWorkflow_simpleTopology1(
+      DN baseDN,
+      DN subordinateDN,
+      DN unrelatedDN
+      )
+  {
+    // Create one DIT set for baseDN and one DIT set for subordinateDN
+    // (no workflow element in any DIT). Create a dummy DIT as well using
+    // the unrelatedDN.
+    WorkflowImpl workflow          = null;
+    WorkflowImpl subWorkflow       = null;
+    WorkflowImpl unrelatedWorkflow = null;
+    {
+      WorkflowElement nullWE = null;
+      workflow    = new WorkflowImpl (baseDN, nullWE);
+      subWorkflow = new WorkflowImpl (subordinateDN, nullWE);
+      if (unrelatedDN != null)
+      {
+        unrelatedWorkflow = new WorkflowImpl (unrelatedDN, nullWE);
+      }
+    }
+
+    // Create a worflow for each dit, no pre/post-workflow element
+    WorkflowTopologyNode w1    = new WorkflowTopologyNode (workflow, null, null);
+    WorkflowTopologyNode w1bis = new WorkflowTopologyNode (workflow, null, null);
+    WorkflowTopologyNode w2    = new WorkflowTopologyNode (subWorkflow, null, null);
+
+    WorkflowTopologyNode w3 = null;
+    if (unrelatedWorkflow != null)
+    {
+       w3 = new WorkflowTopologyNode (unrelatedWorkflow, null, null);
+    }
+
+    // insert status
+    boolean insert;
+
+    // Try to create a topology with unrelated workflows:
+    //
+    //         w1 (baseDN)
+    //         |
+    //         w3 (dnDummy)
+    //
+    // Insert should be rejected
+    if (w3 != null)
+    {
+      insert = w1.insertSubordinate (w3);
+      assertEquals (insert, false);
+    }
+
+    // Try to create a topology with the very same workflow:
+    //
+    //         w1 (baseDN)
+    //         |
+    //         w1 (baseDN)
+    //
+    // Insert should be rejected
+    insert = w1.insertSubordinate (w1);
+    assertEquals (insert, false);
+
+    // Try to create a topology with a workflow whose baseDN is the same than
+    // parent baseDN:
+    //
+    //         w1    (baseDN)
+    //         |
+    //         w1bis (baseDN)
+    //
+    // Insert should be rejected
+    insert = w1.insertSubordinate (w1bis);
+    assertEquals (insert, false);
+
+    // Try to create a topology where subordinate is above the parent:
+    //
+    //         w2 (subordinateDN)
+    //         |
+    //         w1 (baseDN)
+    //
+    // Insert should be rejected
+    insert = w2.insertSubordinate (w1);
+    assertEquals (insert, false);
+
+    // Try to create a clean topology:
+    //
+    //         w1 (baseDN)
+    //         |
+    //         w2 (subordinateDN)
+    //
+    // Expected results:
+    //
+    // - insert should be working
+    insert = w1.insertSubordinate (w2);
+    assertEquals (insert, true);
+
+    // - w1 should be the parent of w2
+    WorkflowTopologyNode parent1 = w2.getParent();
+    assertEquals (parent1, w1);
+
+    // - w2 should be in the w1 subordinate list
+    ArrayList<WorkflowTopologyNode> subordinates1 = w1.getSubordinates();
+    assertEquals (subordinates1.size(), 1);
+    assertEquals (subordinates1.get(0), w2);
+
+    // - w2 should have no subordinate
+    ArrayList<WorkflowTopologyNode> subordinates2 = w2.getSubordinates();
+    assertEquals (subordinates2.size(), 0);
+
+  } // createWorkflow_simpleTopology1
+
+
+  /**
+   * Create a topology with 3 workflows and check that we are getting the
+   * right workflow for a given DN. Then remove a workflow in the chain and
+   * check that topology is properly updated in term of parent/subordinate
+   * links.
+   *
+   *                 W1 (baseDN1)
+   *                 |
+   *                 +----> subordinateDN1
+   *                 |
+   *                 W2 (baseDN2)
+   *                 |
+   *                 +----> subordinateDN2
+   *                 |
+   *                 W3 (baseDN3)
+   *                 |
+   *                 +----> subordinateDN3
+   *                 |
+   *
+   * There is no worklfow element attached to the DITs.
+   *
+   * @param baseDn1         base DN for the top workflow (W1)
+   * @param baseDN2         base DN for the first subordinate workflow (W2)
+   * @param baseDN3         base DN for the second subordinate workflow (W3)
+   * @param subordinateDN1  subordinate DN of baseDN1
+   * @param subordinateDN2  subordinate DN of baseDN2
+   * @param subordinateDN3  subordinate DN of baseDN3
+   * @param unrelatedDN     a DN not registered in any workflow
+   */
+  @Test (dataProvider = "DNSet_2", groups = "virtual")
+  public void createWorkflow_simpleTopology2(
+      DN baseDN1,
+      DN baseDN2,
+      DN baseDN3,
+      DN subordinateDN1,
+      DN subordinateDN2,
+      DN subordinateDN3,
+      DN unrelatedDN
+      )
+  {
+    // Create a worflow for each baseDN, no pre/post-workflow element
+    WorkflowTopologyNode w1;
+    WorkflowTopologyNode w2;
+    WorkflowTopologyNode w3;
+    {
+      // create DITs with the given baseDNs with no workflow element.
+      WorkflowImpl workflow1;
+      WorkflowImpl workflow2;
+      WorkflowImpl workflow3;
+      {
+        WorkflowElement nullWE = null;
+        workflow1 = new WorkflowImpl (baseDN1, nullWE);
+        workflow2 = new WorkflowImpl (baseDN2, nullWE);
+        workflow3 = new WorkflowImpl(baseDN3, nullWE);
+      }
+
+      w1 = new WorkflowTopologyNode (workflow1, null, null);
+      w2 = new WorkflowTopologyNode (workflow2, null, null);
+      w3 = new WorkflowTopologyNode (workflow3, null, null);
+    }
+
+    // insert status
+    boolean insert;
+
+    // Create a first topology with:
+    //
+    //         w1 (baseDN1)
+    //         |
+    //         w3 (baseDN3)
+    //
+    insert = w1.insertSubordinate (w3);
+    assertEquals (insert, true);
+
+    // Now insert w2 between w1 and w3
+    //
+    //         w1 (baseDN1)
+    //         |
+    //         w2 (baseDN2)
+    //         |
+    //         w3 (baseDN3)
+    //
+    insert = w1.insertSubordinate (w2);
+    assertEquals (insert, true);
+
+    // Check the topology:
+    // - w1 has no parent and has only w2 as subordinate
+    WorkflowTopologyNode parent = w1.getParent();
+    assertNull (parent);
+    ArrayList<WorkflowTopologyNode>  subordinates = w1.getSubordinates();
+    assertEquals (subordinates.size(), 1);
+    assertEquals (subordinates.get(0), w2);
+
+    // - w2 has w1 as parent and w3 as subordinate
+    parent = w2.getParent();
+    assertEquals (parent, w1);
+    subordinates = w2.getSubordinates();
+    assertEquals (subordinates.size(), 1);
+    assertEquals (subordinates.get(0), w3);
+
+    // -w3 has w2 as parent and no subordinate
+    parent = w3.getParent();
+    assertEquals (parent, w2);
+    subordinates = w3.getSubordinates();
+    assertEquals (subordinates.size(), 0);
+
+    // ======================================================
+    // Topology is clean, now let's check the route algorithm.
+    // ======================================================
+
+    DN readDN1 = null;
+    DN readDN2 = null;
+    DN readDN3 = null;
+
+    // subordinate1 should be handled by w1 only
+    readDN1 = w1.getParentBaseDN (subordinateDN1);
+    readDN2 = w1.getParentBaseDN (subordinateDN2);
+    readDN3 = w1.getParentBaseDN (subordinateDN3);
+    assertEquals (readDN1, baseDN1);
+    assertEquals (readDN2, baseDN2);
+    assertEquals (readDN3, baseDN3);
+
+    // subordinate2 should be handled by w2 only
+    readDN1 = w2.getParentBaseDN (subordinateDN1);
+    readDN2 = w2.getParentBaseDN (subordinateDN2);
+    readDN3 = w2.getParentBaseDN (subordinateDN3);
+    assertEquals (readDN1, null);
+    assertEquals (readDN2, baseDN2);
+    assertEquals (readDN3, baseDN3);
+
+    // subordinate3 should be handled by w3 only
+    readDN1 = w3.getParentBaseDN (subordinateDN1);
+    readDN2 = w3.getParentBaseDN (subordinateDN2);
+    readDN3 = w3.getParentBaseDN (subordinateDN3);
+    assertEquals (readDN1, null);
+    assertEquals (readDN2, null);
+    assertEquals (readDN3, baseDN3);
+
+    // unrelatedDN should be handled by none of the workflows
+    readDN1 = w1.getParentBaseDN (unrelatedDN);
+    readDN2 = w2.getParentBaseDN (unrelatedDN);
+    readDN3 = w3.getParentBaseDN (unrelatedDN);
+    assertEquals (readDN1, null);
+    assertEquals (readDN2, null);
+    assertEquals (readDN3, null);
+    
+    // ======================================================
+    // Remove a workflow in the chain and check that
+    // the route algorithm is still working
+    // ======================================================
+
+    // Remove w2...
+    //
+    //         w1 (baseDN1)          w1
+    //         |                     |
+    //         w2 (baseDN2)   ==>    |
+    //         |                     |
+    //         w3 (baseDN3)          w3
+    //
+    w2.remove();
+
+    // subordinate1 and subordinate2 should now be handled by w1 only
+    readDN1 = w1.getParentBaseDN (subordinateDN1);
+    readDN2 = w1.getParentBaseDN (subordinateDN2);
+    readDN3 = w1.getParentBaseDN (subordinateDN3);
+    assertEquals (readDN1, baseDN1);
+    assertEquals (readDN2, baseDN1); // was baseDN2 before the removal...
+    assertEquals (readDN3, baseDN3);
+    
+    // sanity check1
+    // subordinate3 should be handled by w3 only
+    readDN1 = w3.getParentBaseDN (subordinateDN1);
+    readDN2 = w3.getParentBaseDN (subordinateDN2);
+    readDN3 = w3.getParentBaseDN (subordinateDN3);
+    assertEquals (readDN1, null);
+    assertEquals (readDN2, null);
+    assertEquals (readDN3, baseDN3);
+    
+    // sanity check2
+    // unrelatedDN should be handled by none of the workflows
+    readDN1 = w1.getParentBaseDN (unrelatedDN);
+    readDN2 = w2.getParentBaseDN (unrelatedDN);
+    readDN3 = w3.getParentBaseDN (unrelatedDN);
+    assertEquals (readDN1, null);
+    assertEquals (readDN2, null);
+    assertEquals (readDN3, null);
+    
+  } // createWorkflow_simpleTopology2
+
+
+  /**
+   * Create a topology of workflows.
+   *
+   *                 W1
+   *               baseDN1
+   *                 /\
+   *                /  \
+   *               /    \
+   *              W2    W3
+   *         baseDN2    baseDN3
+   *
+   * There is no worklfow element attached to the DITs.
+   *
+   * @param baseDn1         base DN for the top workflow (W1)
+   * @param baseDN2         base DN for the first subordinate workflow (W2)
+   * @param baseDN3         base DN for the second subordinate workflow (W3)
+   * @param subordinateDN1  subordinate DN of baseDN1
+   * @param subordinateDN2  subordinate DN of baseDN2
+   * @param subordinateDN3  subordinate DN of baseDN3
+   * @param unrelatedDN     a DN not registered in any workflow
+   */
+  @Test (dataProvider = "DNSet_3", groups = "virtual")
+  public void createWorkflow_complexTopology1 (
+      DN baseDN1,
+      DN baseDN2,
+      DN baseDN3,
+      DN subordinateDN1,
+      DN subordinateDN2,
+      DN subordinateDN3,
+      DN unrelatedDN
+      )
+  {
+    // Create a worflow for each baseDN, no pre/post-workflow element
+    WorkflowTopologyNode w1;
+    WorkflowTopologyNode w2;
+    WorkflowTopologyNode w3;
+    {
+      // create DITs with the given baseDNs with no workflow element.
+      WorkflowImpl workflow1;
+      WorkflowImpl workflow2;
+      WorkflowImpl workflow3;
+      {
+        WorkflowElement nullWE = null;
+
+        workflow1 = new WorkflowImpl (baseDN1, nullWE);
+        workflow2 = new WorkflowImpl (baseDN2, nullWE);
+        workflow3 = new WorkflowImpl (baseDN3, nullWE);
+      }
+
+      w1 = new WorkflowTopologyNode (workflow1, null, null);
+      w2 = new WorkflowTopologyNode (workflow2, null, null);
+      w3 = new WorkflowTopologyNode (workflow3, null, null);
+    }
+
+    // Put all the workflows in a pool
+    WorkflowTopologyNode[] workflowPool = {w1, w2, w3};
+
+    // Create the workflow topology: to do so, try to insert each workflow
+    // in the other workflows. This is basically how workflow topology is
+    // built by the network group.
+    for (WorkflowTopologyNode parent: workflowPool)
+    {
+      for (WorkflowTopologyNode subordinate: workflowPool)
+      {
+        if (parent == subordinate)
+        {
+          // makes no sense to try to insert a workflow in itself!
+          // let's do it anyway... but it should fail ;-)
+          boolean insertDone = parent.insertSubordinate (parent);
+          assertEquals (insertDone, false);
+        }
+        else
+        {
+          if (parent.insertSubordinate (subordinate))
+          {
+            // insert done
+          }
+        }
+      }
+    }
+
+    // Check the topology
+    // ------------------
+
+    // W1 should have 2 subordinates: W2 and W3
+    ArrayList<WorkflowTopologyNode> subordinates1 = w1.getSubordinates();
+    assertEquals (subordinates1.size(), 2);
+
+    // W2 and W3 should have no subordinate
+    ArrayList<WorkflowTopologyNode> subordinates2 = w2.getSubordinates();
+    assertEquals (subordinates2.size(), 0);
+    ArrayList<WorkflowTopologyNode> subordinates3 = w3.getSubordinates();
+    assertEquals (subordinates3.size(), 0);
+
+    // W1 should be the parent of W2 and W3
+    WorkflowTopologyNode parent2 = w2.getParent();
+    assertEquals (parent2, w1);
+    WorkflowTopologyNode parent3 = w3.getParent();
+    assertEquals (parent3, w1);
+
+    // Check the route algorithm
+    // -------------------------
+
+    // candidate for baseDN1 and subordinateBaseDN1 should be W1
+    WorkflowTopologyNode candidate1 = w1.getWorkflowCandidate (baseDN1);
+    assertEquals (candidate1, w1);
+    candidate1 = w1.getWorkflowCandidate (subordinateDN1);
+    assertEquals (candidate1, w1);
+
+    // candidate for baseDN2/3 and subordinateBaseDN2/3 should be W2/3
+    WorkflowTopologyNode candidate2 = w1.getWorkflowCandidate (baseDN2);
+    assertEquals (candidate2, w2);
+    candidate2 = w1.getWorkflowCandidate (subordinateDN2);
+    assertEquals (candidate2, w2);
+
+    WorkflowTopologyNode candidate3 = w1.getWorkflowCandidate (baseDN3);
+    assertEquals (candidate3, w3);
+    candidate3 = w1.getWorkflowCandidate (subordinateDN3);
+    assertEquals (candidate3, w3);
+
+    // there should be no candidate for dummyDN
+    if (unrelatedDN != null)
+    {
+      WorkflowTopologyNode candidateDummy = w1.getWorkflowCandidate (unrelatedDN);
+      assertEquals (candidateDummy, null);
+    }
+
+    // dump the topology
+    StringBuffer sb = w1.toString ("");
+    System.out.println (sb);
+
+  } // createWorkflow_complexTopology1
+
+
+  /**
+   * Test the elaboration of the global result code by the workflow.
+   */
+  @Test (dataProvider = "ResultCodes_1", groups = "virtual")
+  public void testGlobalResultCode(
+      ResultCode receivedResultCode,
+      ResultCode initialResultCode,
+      ResultCode expectedGlobalResultCode
+      )
+      throws Exception
+  {
+    // Check the function that elaborates the global result code
+    WorkflowResultCode globalResultCode = new WorkflowResultCode (
+        initialResultCode, new StringBuilder("")
+        );
+    globalResultCode.elaborateGlobalResultCode (
+        receivedResultCode, new StringBuilder("")
+        );
+    assertEquals (globalResultCode.resultCode(), expectedGlobalResultCode);
+  }
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java
index 683a7a8..5ee6d9a 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java
@@ -34,7 +34,7 @@
 import org.testng.annotations.Test;
 
 import org.opends.server.TestCaseUtils;
-import org.opends.server.core.BindOperation;
+import org.opends.server.core.BindOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.tools.LDAPSearch;
@@ -137,8 +137,8 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    BindOperation bindOperation =
-         new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                            new ArrayList<Control>(), "3", DN.nullDN(),
                            SASL_MECHANISM_ANONYMOUS, null);
     handler.processSASLBind(bindOperation);
@@ -164,8 +164,8 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    BindOperation bindOperation =
-         new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                            new ArrayList<Control>(), "3", DN.nullDN(),
                            SASL_MECHANISM_ANONYMOUS, new ASN1OctetString());
     handler.processSASLBind(bindOperation);
@@ -190,8 +190,8 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    BindOperation bindOperation =
-         new BindOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    BindOperationBasis bindOperation =
+         new BindOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                            new ArrayList<Control>(), "3", DN.nullDN(),
                            SASL_MECHANISM_ANONYMOUS,
                            new ASN1OctetString("Internal Trace String"));
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java
index 7edd89a..1503e26 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java
@@ -40,22 +40,18 @@
 import org.opends.server.admin.std.meta.AttributeValuePasswordValidatorCfgDefn;
 import org.opends.server.admin.std.server.AttributeValuePasswordValidatorCfg;
 import org.opends.server.admin.server.AdminTestCaseUtils;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.ByteString;
-import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.Control;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
-import org.opends.server.types.ResultCode;
 
 import static org.testng.Assert.*;
 
@@ -418,8 +414,8 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java
index db2989d..38ce1a3 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java
@@ -28,7 +28,6 @@
 
 
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -41,22 +40,18 @@
 import org.opends.server.admin.std.meta.CharacterSetPasswordValidatorCfgDefn;
 import org.opends.server.admin.std.server.CharacterSetPasswordValidatorCfg;
 import org.opends.server.admin.server.AdminTestCaseUtils;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.ByteString;
-import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.Control;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
-import org.opends.server.types.ResultCode;
 
 import static org.testng.Assert.*;
 
@@ -535,8 +530,8 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java
index ed7754b..cf50914 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java
@@ -28,9 +28,6 @@
 
 
 
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -43,22 +40,18 @@
 import org.opends.server.admin.std.meta.DictionaryPasswordValidatorCfgDefn;
 import org.opends.server.admin.std.server.DictionaryPasswordValidatorCfg;
 import org.opends.server.admin.server.AdminTestCaseUtils;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.ByteString;
-import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.Control;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
-import org.opends.server.types.ResultCode;
 
 import static org.testng.Assert.*;
 
@@ -502,8 +495,8 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java
index dd6ff2f..0dcfe9b 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java
@@ -55,6 +55,7 @@
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchScope;
 import org.opends.server.types.VirtualAttributeRule;
+import org.opends.server.workflowelement.localbackend.LocalBackendSearchOperation;
 
 import static org.testng.Assert.*;
 
@@ -1101,7 +1102,10 @@
                                      SearchScope.WHOLE_SUBTREE,
                                      DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                      0, false, filter, null, null);
-    provider.processSearch(rule, searchOperation);
+    LocalBackendSearchOperation localSearch =
+      new LocalBackendSearchOperation(searchOperation);
+    
+    provider.processSearch(rule, localSearch);
 
     if (shouldMatch)
     {
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java
index 5487dbb..e1ae3a8 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java
@@ -57,6 +57,7 @@
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchScope;
 import org.opends.server.types.VirtualAttributeRule;
+import org.opends.server.workflowelement.localbackend.LocalBackendSearchOperation;
 
 import static org.testng.Assert.*;
 
@@ -1113,7 +1114,7 @@
                                      DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                      0, false, filter, null, null);
 
-    assertEquals(provider.isSearchable(rule, searchOperation), isSearchable);
+    assertEquals(provider.isSearchable(rule, new LocalBackendSearchOperation(searchOperation)), isSearchable);
   }
 
 
@@ -1237,7 +1238,7 @@
                                      SearchScope.WHOLE_SUBTREE,
                                      DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                      0, false, filter, null, null);
-    provider.processSearch(rule, searchOperation);
+    provider.processSearch(rule, new LocalBackendSearchOperation(searchOperation));
 
     boolean matchFound = false;
     for (Entry e : searchOperation.getSearchEntries())
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LengthBasedPasswordValidatorTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LengthBasedPasswordValidatorTestCase.java
index 8c7d071..7e16c57 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LengthBasedPasswordValidatorTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LengthBasedPasswordValidatorTestCase.java
@@ -45,7 +45,7 @@
 import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.Attribute;
@@ -374,8 +374,8 @@
 
       InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
-      ModifyOperation op =
-           new ModifyOperation(conn, conn.nextOperationID(),
+      ModifyOperationBasis op =
+           new ModifyOperationBasis(conn, conn.nextOperationID(),
                                conn.nextMessageID(), new ArrayList<Control>(),
                                DN.decode("cn=uid=test.user,o=test"), mods);
 
@@ -447,8 +447,8 @@
 
       InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
-      ModifyOperation op =
-           new ModifyOperation(conn, conn.nextOperationID(),
+      ModifyOperationBasis op =
+           new ModifyOperationBasis(conn, conn.nextOperationID(),
                                conn.nextMessageID(), new ArrayList<Control>(),
                                DN.decode("cn=uid=test.user,o=test"), mods);
 
@@ -522,8 +522,8 @@
 
       InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
-      ModifyOperation op =
-           new ModifyOperation(conn, conn.nextOperationID(),
+      ModifyOperationBasis op =
+           new ModifyOperationBasis(conn, conn.nextOperationID(),
                                conn.nextMessageID(), new ArrayList<Control>(),
                                DN.decode("cn=uid=test.user,o=test"), mods);
 
@@ -597,8 +597,8 @@
 
       InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
-      ModifyOperation op =
-           new ModifyOperation(conn, conn.nextOperationID(),
+      ModifyOperationBasis op =
+           new ModifyOperationBasis(conn, conn.nextOperationID(),
                                conn.nextMessageID(), new ArrayList<Control>(),
                                DN.decode("cn=uid=test.user,o=test"), mods);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidatorTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidatorTestCase.java
index c0672b7..df4b95d 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidatorTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidatorTestCase.java
@@ -42,10 +42,8 @@
 import org.opends.server.admin.std.server.
             RepeatedCharactersPasswordValidatorCfg;
 import org.opends.server.admin.server.AdminTestCaseUtils;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.Attribute;
@@ -316,8 +314,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -384,8 +383,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -451,8 +451,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -519,8 +520,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -585,8 +587,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -652,8 +655,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SimilarityBasedPasswordValidatorTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SimilarityBasedPasswordValidatorTestCase.java
index e82d47e..35f5c45 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SimilarityBasedPasswordValidatorTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SimilarityBasedPasswordValidatorTestCase.java
@@ -36,7 +36,7 @@
 import org.testng.annotations.Test;
 import org.opends.server.TestCaseUtils;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.Attribute;
@@ -291,8 +291,8 @@
 
       InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
-      ModifyOperation op =
-           new ModifyOperation(conn, conn.nextOperationID(),
+      ModifyOperationBasis op =
+           new ModifyOperationBasis(conn, conn.nextOperationID(),
                                conn.nextMessageID(), new ArrayList<Control>(),
                                DN.decode("cn=uid=test.user,o=test"), mods);
 
@@ -367,8 +367,8 @@
 
       InternalClientConnection conn =
            InternalClientConnection.getRootConnection();
-      ModifyOperation op =
-           new ModifyOperation(conn, conn.nextOperationID(),
+      ModifyOperationBasis op =
+           new ModifyOperationBasis(conn, conn.nextOperationID(),
                                conn.nextMessageID(), new ArrayList<Control>(),
                                DN.decode("cn=uid=test.user,o=test"), mods);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidatorTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidatorTestCase.java
index d5e4fe0..7339811 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidatorTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidatorTestCase.java
@@ -42,10 +42,8 @@
 import org.opends.server.admin.std.server.
             UniqueCharactersPasswordValidatorCfg;
 import org.opends.server.admin.server.AdminTestCaseUtils;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.Attribute;
@@ -316,8 +314,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -384,8 +383,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+      new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -450,9 +450,10 @@
                               new Attribute("userpassword", "pasSw")));
 
     InternalClientConnection conn =
-         InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+      InternalClientConnection.getRootConnection();
+    ModifyOperationBasis modifyOperation =
+      new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -519,8 +520,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+      new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -585,8 +587,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
@@ -652,8 +655,9 @@
 
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(),
+                             conn.nextMessageID(),
                              new ArrayList<Control>(),
                              DN.decode("uid=test.user,o=test"), mods);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java
index b70acc7..3933296 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java
@@ -28,16 +28,18 @@
 
 
 
+import static org.opends.server.util.ServerConstants.OID_WHO_AM_I_REQUEST;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashSet;
 
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
 import org.opends.server.TestCaseUtils;
-import org.opends.server.api.ClientConnection;
 import org.opends.server.api.ConnectionSecurityProvider;
 import org.opends.server.core.AddOperation;
 import org.opends.server.core.BindOperation;
@@ -45,23 +47,23 @@
 import org.opends.server.core.DeleteOperation;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ExtendedOperation;
-import org.opends.server.core.ModifyOperation;
 import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.core.ModifyOperation;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.ldap.LDAPAttribute;
 import org.opends.server.protocols.ldap.LDAPFilter;
 import org.opends.server.protocols.ldap.LDAPModification;
+import org.opends.server.types.AbstractOperation;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.CancelRequest;
 import org.opends.server.types.CancelResult;
+import org.opends.server.types.DN;
 import org.opends.server.types.DereferencePolicy;
 import org.opends.server.types.DisconnectReason;
-import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
-import org.opends.server.types.Operation;
 import org.opends.server.types.RawAttribute;
 import org.opends.server.types.RawModification;
 import org.opends.server.types.RDN;
@@ -69,10 +71,9 @@
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchResultReference;
 import org.opends.server.types.SearchScope;
-
-import static org.testng.Assert.*;
-
-import static org.opends.server.util.ServerConstants.*;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
 
 
 
@@ -1068,7 +1069,7 @@
   {
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
-    Collection<Operation> opList = conn.getOperationsInProgress();
+    Collection<AbstractOperation> opList = conn.getOperationsInProgress();
     assertNotNull(opList);
     assertTrue(opList.isEmpty());
   }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java
index 001c295..a2c57a4 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java
@@ -51,8 +51,8 @@
 import org.opends.server.admin.std.meta.JMXConnectionHandlerCfgDefn;
 import org.opends.server.admin.std.server.JMXConnectionHandlerCfg;
 import org.opends.server.config.JMXMBean;
-import org.opends.server.core.AddOperation;
-import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.AddOperationBasis;
+import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.ConfigChangeResult;
@@ -221,7 +221,7 @@
                 + serverJmxPort, "cn: JMX Connection Handler");
     InternalClientConnection connection =
       InternalClientConnection.getRootConnection();
-    AddOperation addOp = new AddOperation(connection,
+    AddOperationBasis addOp = new AddOperationBasis(connection,
         InternalClientConnection.nextOperationID(),
         InternalClientConnection.nextMessageID(), null,
         newJmxConnectionJmx.getDN(), newJmxConnectionJmx
@@ -259,7 +259,7 @@
     // cleanup client connection
     connector.close();
     jmxcDisabled.close();
-    DeleteOperation delOp = new DeleteOperation(connection,
+    DeleteOperationBasis delOp = new DeleteOperationBasis(connection,
         InternalClientConnection.nextOperationID(),
         InternalClientConnection.nextMessageID(), null,
         newJmxConnectionJmx.getDN());
@@ -588,4 +588,5 @@
 
     mbsc.setAttribute(name, attr);
   }
+
 }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxTestCase.java
index 7f9dcd8..d039bdf 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxTestCase.java
@@ -35,7 +35,7 @@
 import org.opends.server.TestCaseUtils;
 import org.opends.server.api.ConnectionHandler;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.Control;
 import org.opends.server.types.DN;
@@ -127,7 +127,7 @@
     mods.add(new Modification(ModificationType.REPLACE,
         new org.opends.server.types.Attribute(
             "ds-cfg-connection-handler-enabled", "true")));
-    ModifyOperation op = new ModifyOperation(
+    ModifyOperationBasis op = new ModifyOperationBasis(
         conn,
         conn.nextOperationID(),
         conn.nextMessageID(),
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/InitOnLineTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/InitOnLineTest.java
index 1d774fb..2f6f0c8 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/InitOnLineTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/InitOnLineTest.java
@@ -26,11 +26,20 @@
  */
 package org.opends.server.replication;
 
-import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.config.ConfigConstants.ATTR_TASK_COMPLETION_TIME;
+import static org.opends.server.config.ConfigConstants.ATTR_TASK_INITIALIZE_DONE;
+import static org.opends.server.config.ConfigConstants.ATTR_TASK_INITIALIZE_LEFT;
+import static org.opends.server.config.ConfigConstants.ATTR_TASK_LOG_MESSAGES;
+import static org.opends.server.config.ConfigConstants.ATTR_TASK_STATE;
 import static org.opends.server.loggers.ErrorLogger.logError;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import org.opends.server.loggers.debug.DebugTracer;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
 import static org.opends.server.messages.MessageHandler.getMessage;
+import static org.opends.server.messages.ReplicationMessages.MSGID_INVALID_IMPORT_SOURCE;
+import static org.opends.server.messages.ReplicationMessages.MSGID_NO_MATCHING_DOMAIN;
+import static org.opends.server.messages.ReplicationMessages.MSGID_NO_REACHABLE_PEER_IN_THE_DOMAIN;
+import static org.opends.server.messages.ReplicationMessages.MSGID_SIMULTANEOUS_IMPORT_EXPORT_REJECTED;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
@@ -42,7 +51,9 @@
 import org.opends.server.TestCaseUtils;
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.messages.TaskMessages;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.internal.InternalSearchOperation;
@@ -53,11 +64,11 @@
 import org.opends.server.replication.protocol.ErrorMessage;
 import org.opends.server.replication.protocol.InitializeRequestMessage;
 import org.opends.server.replication.protocol.InitializeTargetMessage;
+import org.opends.server.replication.protocol.ReplicationMessage;
 import org.opends.server.replication.protocol.RoutableMessage;
 import org.opends.server.replication.protocol.SocketSession;
-import org.opends.server.replication.protocol.ReplicationMessage;
-import org.opends.server.replication.server.ReplicationServer;
 import org.opends.server.replication.server.ReplServerFakeConfiguration;
+import org.opends.server.replication.server.ReplicationServer;
 import org.opends.server.schema.DirectoryStringSyntax;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.DN;
@@ -69,8 +80,6 @@
 import org.opends.server.types.SearchScope;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
-import static org.opends.server.messages.ReplicationMessages.*;
-import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
 
 /**
  * Tests contained here:
@@ -564,7 +573,7 @@
       for (String ldifEntry : updatedEntries)
       {
         Entry entry = TestCaseUtils.entryFromLdifString(ldifEntry);
-        AddOperation addOp = new AddOperation(connection,
+        AddOperationBasis addOp = new AddOperationBasis(connection,
             InternalClientConnection.nextOperationID(), InternalClientConnection
             .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
             entry.getUserAttributes(), entry.getOperationalAttributes());
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ProtocolWindowTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ProtocolWindowTest.java
index f6921f5..82e4e8c 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ProtocolWindowTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ProtocolWindowTest.java
@@ -31,14 +31,13 @@
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
-import java.io.IOException;
 import java.net.ServerSocket;
 import java.net.SocketTimeoutException;
 import java.util.ArrayList;
 import java.util.List;
 
 import org.opends.server.TestCaseUtils;
-import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyOperation;
 import org.opends.server.protocols.asn1.ASN1OctetString;
@@ -115,7 +114,7 @@
 
       // Create an Entry (add operation) that will be later used in the test.
       Entry tmp = personEntry.duplicate(false);
-      AddOperation addOp = new AddOperation(connection,
+      AddOperationBasis addOp = new AddOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
           .nextMessageID(), null, tmp.getDN(),
           tmp.getObjectClasses(), tmp.getUserAttributes(),
@@ -255,7 +254,7 @@
     for (int i = 0; i < topEntries.length; i++)
     {
       entry = TestCaseUtils.entryFromLdifString(topEntries[i]);
-      AddOperation addOp = new AddOperation(connection,
+      AddOperationBasis addOp = new AddOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
               .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
           entry.getUserAttributes(), entry.getOperationalAttributes());
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReSyncTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReSyncTest.java
index b6b76e1..9a46722 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReSyncTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReSyncTest.java
@@ -33,9 +33,8 @@
 import java.util.UUID;
 
 import org.opends.server.TestCaseUtils;
-import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.protocols.internal.InternalClientConnection;
-
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.opends.server.types.ResultCode;
@@ -123,14 +122,15 @@
   private ResultCode addEntry(String entryString) throws Exception
   {
     Entry entry;
-    AddOperation addOp;
+    AddOperationBasis addOp;
     entry = TestCaseUtils.entryFromLdifString(entryString);
-    addOp = new AddOperation(connection,
+    addOp = new AddOperationBasis(connection,
        InternalClientConnection.nextOperationID(), InternalClientConnection
        .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
        entry.getUserAttributes(), entry.getOperationalAttributes());
     addOp.setInternalOperation(true);
     addOp.run();
+    
     entryList.add(entry.getDN());
     return addOp.getResultCode();
   }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
index dfaa5eb..7ab93ba 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
@@ -31,52 +31,50 @@
 import static org.opends.server.loggers.ErrorLogger.logError;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.fail;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 import java.net.SocketException;
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
-import java.util.NoSuchElementException;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.concurrent.locks.Lock;
 
 import org.opends.server.DirectoryServerTestCase;
 import org.opends.server.TestCaseUtils;
-import org.opends.server.replication.common.ServerState;
-import org.opends.server.replication.plugin.ReplicationBroker;
-import org.opends.server.replication.plugin.ReplicationDomain;
-import org.opends.server.replication.plugin.PersistentServerState;
-import org.opends.server.schema.DirectoryStringSyntax;
-import org.opends.server.schema.IntegerSyntax;
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.core.AddOperation;
-import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.internal.InternalSearchOperation;
 import org.opends.server.protocols.ldap.LDAPFilter;
+import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.plugin.PersistentServerState;
+import org.opends.server.replication.plugin.ReplicationBroker;
+import org.opends.server.schema.DirectoryStringSyntax;
+import org.opends.server.schema.IntegerSyntax;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.ByteStringFactory;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
-import org.opends.server.types.ByteStringFactory;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.LockManager;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
-import org.opends.server.types.SearchScope;
 import org.opends.server.types.SearchResultEntry;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.LockManager;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeValue;
+import org.opends.server.types.SearchScope;
 import org.opends.server.util.TimeThread;
 import org.testng.annotations.AfterClass;
-import org.testng.annotations.Test;
-
 import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
 
 /**
  * An abstract class that all Replication unit test should extend.
@@ -254,7 +252,7 @@
         ErrorLogSeverity.NOTICE,
         "ReplicationTestCase/Cleaning config entries" , 1);
 
-    DeleteOperation op;
+    DeleteOperationBasis op;
     // Delete entries
     try
     {
@@ -265,10 +263,9 @@
             ErrorLogSeverity.NOTICE,
             "cleaning config entry " + dn, 1);
 
-        op = new DeleteOperation(connection, InternalClientConnection
+        op = new DeleteOperationBasis(connection, InternalClientConnection
             .nextOperationID(), InternalClientConnection.nextMessageID(), null,
             dn);
-
         op.run();
       }
     }
@@ -286,7 +283,7 @@
         ErrorLogSeverity.NOTICE,
         "ReplicationTestCase/Cleaning entries" , 1);
 
-    DeleteOperation op;
+    DeleteOperationBasis op;
     // Delete entries
     try
     {
@@ -297,7 +294,7 @@
             ErrorLogSeverity.NOTICE,
             "cleaning entry " + dn, 1);
 
-        op = new DeleteOperation(connection, InternalClientConnection
+        op = new DeleteOperationBasis(connection, InternalClientConnection
             .nextOperationID(), InternalClientConnection.nextMessageID(), null,
             dn);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
index 8468f5a..0296bcd 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
@@ -42,6 +42,7 @@
 import org.opends.server.api.SynchronizationProvider;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.replication.common.ChangeNumberGenerator;
 import org.opends.server.replication.plugin.ReplicationBroker;
@@ -149,7 +150,7 @@
       List<Modification> mods = new ArrayList<Modification>();
       Modification mod = new Modification(ModificationType.ADD, attr);
       mods.add(mod);
-      ModifyOperation modOp = new ModifyOperation(connection,
+      ModifyOperationBasis modOp = new ModifyOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
           .nextMessageID(), null, baseDn, mods);
       modOp.setInternalOperation(true);
@@ -192,7 +193,7 @@
       mod = new Modification(ModificationType.DELETE, attr);
       mods.clear();
       mods.add(mod);
-      modOp = new ModifyOperation(connection,
+      modOp = new ModifyOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
           .nextMessageID(), null, baseDn, mods);
       modOp.setInternalOperation(true);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/StressTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/StressTest.java
index 24b2f0d..6341c62 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/StressTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/StressTest.java
@@ -40,7 +40,7 @@
 import org.opends.server.admin.std.server.MonitorProviderCfg;
 import org.opends.server.api.MonitorProvider;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyOperation;
 import org.opends.server.protocols.internal.InternalClientConnection;
@@ -104,7 +104,7 @@
 
       // Create an Entry (add operation) that will be later used in the test.
       Entry tmp = personEntry.duplicate(false);
-      AddOperation addOp = new AddOperation(connection,
+      AddOperationBasis addOp = new AddOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
           .nextMessageID(), null, tmp.getDN(),
           tmp.getObjectClasses(), tmp.getUserAttributes(),
@@ -193,7 +193,7 @@
     for (int i = 0; i < topEntries.length; i++)
     {
       entry = TestCaseUtils.entryFromLdifString(topEntries[i]);
-      AddOperation addOp = new AddOperation(connection,
+      AddOperationBasis addOp = new AddOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
               .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
           entry.getUserAttributes(), entry.getOperationalAttributes());
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
index 3b63fc5..c93ae7f 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
@@ -28,7 +28,12 @@
 package org.opends.server.replication;
 
 import static org.opends.server.loggers.ErrorLogger.logError;
-import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 import java.net.ServerSocket;
 import java.util.ArrayList;
@@ -37,7 +42,17 @@
 import java.util.concurrent.locks.Lock;
 
 import org.opends.server.TestCaseUtils;
+import org.opends.server.core.AddOperationBasis;
+import org.opends.server.core.DeleteOperationBasis;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.plugins.ShortCircuitPlugin;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.ldap.LDAPAttribute;
+import org.opends.server.protocols.ldap.LDAPModification;
 import org.opends.server.replication.common.ChangeNumber;
 import org.opends.server.replication.common.ChangeNumberGenerator;
 import org.opends.server.replication.plugin.ReplicationBroker;
@@ -49,16 +64,21 @@
 import org.opends.server.replication.protocol.ModifyMsg;
 import org.opends.server.replication.protocol.ReplicationMessage;
 import org.opends.server.schema.DirectoryStringSyntax;
-import org.opends.server.core.AddOperation;
-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.protocols.asn1.ASN1OctetString;
-import org.opends.server.protocols.internal.InternalClientConnection;
-import org.opends.server.protocols.ldap.LDAPAttribute;
-import org.opends.server.protocols.ldap.LDAPModification;
-import org.opends.server.types.*;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.LockManager;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.Operation;
+import org.opends.server.types.OperationType;
+import org.opends.server.types.RDN;
+import org.opends.server.types.RawModification;
+import org.opends.server.types.ResultCode;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
@@ -246,7 +266,7 @@
    */
   private void addEntry(Entry entry) throws Exception
   {
-    AddOperation addOp = new AddOperation(connection,
+    AddOperationBasis addOp = new AddOperationBasis(connection,
         InternalClientConnection.nextOperationID(), InternalClientConnection
             .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
         entry.getUserAttributes(), entry.getOperationalAttributes());
@@ -933,7 +953,7 @@
     for (String entryStr : topEntries)
     {
       entry = TestCaseUtils.entryFromLdifString(entryStr);
-      AddOperation addOp = new AddOperation(connection,
+      AddOperationBasis addOp = new AddOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
           .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
           entry.getUserAttributes(), entry.getOperationalAttributes());
@@ -974,7 +994,7 @@
          + "objectClass: organizationalUnit\n"
          + "entryUUID: 66666666-6666-6666-6666-666666666666\n";
     entry = TestCaseUtils.entryFromLdifString(p2);
-    AddOperation addOp = new AddOperation(connection,
+    AddOperationBasis addOp = new AddOperationBasis(connection,
         InternalClientConnection.nextOperationID(), InternalClientConnection
         .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
         entry.getUserAttributes(), entry.getOperationalAttributes());
@@ -1122,7 +1142,7 @@
 
       // Create an Entry (add operation)
       Entry tmp = personEntry.duplicate(false);
-      AddOperation addOp = new AddOperation(connection,
+      AddOperationBasis addOp = new AddOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
           .nextMessageID(), null, tmp.getDN(),
           tmp.getObjectClasses(), tmp.getUserAttributes(),
@@ -1151,7 +1171,7 @@
       // Modify the entry
       List<Modification> mods = generatemods("telephonenumber", "01 02 45");
 
-      ModifyOperation modOp = new ModifyOperation(connection,
+      ModifyOperationBasis modOp = new ModifyOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
           .nextMessageID(), null, personEntry.getDN(), mods);
       modOp.setInternalOperation(true);
@@ -1191,7 +1211,7 @@
       "The received MODIFY_DN message is not for the excepted DN");
 
       // Delete the entry
-      DeleteOperation delOp = new DeleteOperation(connection,
+      DeleteOperationBasis delOp = new DeleteOperationBasis(connection,
           InternalClientConnection.nextOperationID(), InternalClientConnection
           .nextMessageID(), null, DN
           .decode("uid= new person,ou=People,dc=example,dc=com"));
@@ -1357,8 +1377,8 @@
         "Starting replication test : deleteNoSuchObject" , 1);
 
     DN dn = DN.decode("cn=No Such Object,ou=People,dc=example,dc=com");
-    Operation op =
-         new DeleteOperation(connection,
+    DeleteOperationBasis op =
+         new DeleteOperationBasis(connection,
                              InternalClientConnection.nextOperationID(),
                              InternalClientConnection.nextMessageID(), null,
                              dn);
@@ -1404,8 +1424,8 @@
           + "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
           + "userPassword: password\n" + "initials: AA\n";
       Entry tmp = TestCaseUtils.entryFromLdifString(personLdif);
-      AddOperation addOp =
-           new AddOperation(connection,
+      AddOperationBasis addOp =
+           new AddOperationBasis(connection,
                             InternalClientConnection.nextOperationID(),
                             InternalClientConnection.nextMessageID(),
                             null, tmp.getDN(), tmp.getObjectClasses(),
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java
index ef37b40..15c2203 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java
@@ -39,8 +39,9 @@
 import static org.opends.server.replication.protocol.OperationContext.*;
 
 import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.replication.ReplicationTestCase;
@@ -693,7 +694,7 @@
     List<Modification> mods = new ArrayList<Modification>();
     mods.add(mod);
 
-    ModifyOperation modOp = new ModifyOperation(connection, 1, 1, null,
+    ModifyOperationBasis modOp = new ModifyOperationBasis(connection, 1, 1, null,
         entry.getDN(), mods);
     ModifyContext ctx = new ModifyContext(t, "uniqueId");
     modOp.setAttachment(SYNCHROCONTEXT, ctx);
@@ -701,7 +702,7 @@
     hist.replayOperation(modOp, entry);
     if (mod.getModificationType() == ModificationType.ADD)
     {
-      AddOperation addOp = new AddOperation(connection, 1, 1, null, entry
+      AddOperationBasis addOp = new AddOperationBasis(connection, 1, 1, null, entry
           .getDN(), entry.getObjectClasses(), entry.getUserAttributes(),
           entry.getOperationalAttributes());
       testHistorical(hist, addOp);
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentServerStateTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentServerStateTest.java
index 83e65a0..f461e1f 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentServerStateTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentServerStateTest.java
@@ -29,12 +29,11 @@
 import static org.testng.Assert.assertEquals;
 
 import org.opends.server.TestCaseUtils;
-import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.replication.ReplicationTestCase;
 import org.opends.server.replication.common.ChangeNumber;
 import org.opends.server.replication.common.ChangeNumberGenerator;
-import org.opends.server.replication.plugin.PersistentServerState;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.testng.annotations.BeforeClass;
@@ -65,7 +64,7 @@
 
     connection = InternalClientConnection.getRootConnection();
     Entry entry = TestCaseUtils.entryFromLdifString(topEntry);
-    AddOperation addOp = new AddOperation(connection,
+    AddOperationBasis addOp = new AddOperationBasis(connection,
         InternalClientConnection.nextOperationID(), InternalClientConnection
         .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
         entry.getUserAttributes(), entry.getOperationalAttributes());
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentStateTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentStateTest.java
index d39d45f..6206ecf 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentStateTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentStateTest.java
@@ -29,12 +29,11 @@
 import static org.testng.Assert.assertEquals;
 
 import org.opends.server.TestCaseUtils;
-import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.replication.ReplicationTestCase;
 import org.opends.server.replication.common.ChangeNumber;
 import org.opends.server.replication.common.ChangeNumberGenerator;
-import org.opends.server.replication.plugin.PersistentServerState;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.testng.annotations.BeforeClass;
@@ -65,7 +64,7 @@
 
     connection = InternalClientConnection.getRootConnection();
     Entry entry = TestCaseUtils.entryFromLdifString(topEntry);
-    AddOperation addOp = new AddOperation(connection,
+    AddOperationBasis addOp = new AddOperationBasis(connection,
         InternalClientConnection.nextOperationID(), InternalClientConnection
         .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
         entry.getUserAttributes(), entry.getOperationalAttributes());
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
index f51c762..bd48b77 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
@@ -40,10 +40,11 @@
 import java.util.zip.DataFormatException;
 
 import org.opends.server.core.AddOperation;
-import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.AddOperationBasis;
+import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyDNOperation;
-import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.replication.ReplicationTestCase;
 import org.opends.server.replication.common.ChangeNumber;
@@ -175,11 +176,11 @@
     Operation op = msg.createOperation(connection);
     Operation generatedOperation = generatedMsg.createOperation(connection);
 
-    assertEquals(op.getClass(), ModifyOperation.class);
-    assertEquals(generatedOperation.getClass(), ModifyOperation.class);
+    assertEquals(op.getClass(), ModifyOperationBasis.class);
+    assertEquals(generatedOperation.getClass(), ModifyOperationBasis.class);
 
-    ModifyOperation mod1 = (ModifyOperation) op;
-    ModifyOperation mod2 = (ModifyOperation) generatedOperation;
+    ModifyOperationBasis mod1 = (ModifyOperationBasis) op;
+    ModifyOperationBasis mod2 = (ModifyOperationBasis) generatedOperation;
 
     assertEquals(mod1.getRawEntryDN(), mod2.getRawEntryDN());
     assertEquals( mod1.getAttachment(SYNCHROCONTEXT),
@@ -259,7 +260,7 @@
   {
     InternalClientConnection connection =
         InternalClientConnection.getRootConnection();
-    DeleteOperation op = new DeleteOperation(connection, 1, 1,null,
+    DeleteOperationBasis op = new DeleteOperationBasis(connection, 1, 1,null,
                                              DN.decode(rawDN));
     ChangeNumber cn = new ChangeNumber(TimeThread.getTime(),
       (short) 123, (short) 45);
@@ -274,9 +275,9 @@
 
     Operation generatedOperation = generatedMsg.createOperation(connection);
 
-    assertEquals(generatedOperation.getClass(), DeleteOperation.class);
+    assertEquals(generatedOperation.getClass(), DeleteOperationBasis.class);
 
-    DeleteOperation mod2 = (DeleteOperation) generatedOperation;
+    DeleteOperationBasis mod2 = (DeleteOperationBasis) generatedOperation;
 
     assertEquals(op.getRawEntryDN(), mod2.getRawEntryDN());
 
@@ -402,7 +403,7 @@
     //Create an Add operation and generate and Add msg from it
     DN dn = DN.decode(rawDN);
 
-    addOp = new AddOperation(connection,
+    addOp = new AddOperationBasis(connection,
         (long) 1, 1, null, dn, objectClassList, userAttList, opList);
     OperationContext opCtx = new AddContext(cn, "thisIsaUniqueID",
         "parentUniqueId");
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/UpdateComparatorTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/UpdateComparatorTest.java
index cc57d13..a287160 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/UpdateComparatorTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/UpdateComparatorTest.java
@@ -34,6 +34,7 @@
 
 
 import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.replication.ReplicationTestCase;
 import org.opends.server.replication.common.ChangeNumber;
@@ -70,7 +71,7 @@
     DeleteOperation op = null;
     try
     {
-      op = new DeleteOperation(connection, 1, 1,null,
+      op = new DeleteOperationBasis(connection, 1, 1,null,
                                                DN.decode("dc=com"));
     }
     catch (DirectoryException e)
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
index fa60bcf..bdd0828 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
@@ -28,30 +28,32 @@
 
 
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileWriter;
 import java.util.ArrayList;
 import java.util.UUID;
 
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
 import org.opends.server.TestCaseUtils;
-import static org.opends.server.util.StaticUtils.createEntry;
 import org.opends.server.backends.task.Task;
 import org.opends.server.backends.task.TaskBackend;
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.controls.ProxiedAuthV1Control;
 import org.opends.server.controls.ProxiedAuthV2Control;
 import org.opends.server.core.AddOperation;
+import org.opends.server.core.AddOperationBasis;
 import org.opends.server.core.CompareOperation;
 import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DeleteOperationBasis;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.ModifyOperation;
 import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.ModifyOperationBasis;
 import org.opends.server.core.SchemaConfigManager;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 import org.opends.server.protocols.internal.InternalClientConnection;
@@ -59,16 +61,10 @@
 import org.opends.server.tools.LDAPModify;
 import org.opends.server.tools.LDAPPasswordModify;
 import org.opends.server.tools.LDAPSearch;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AuthenticationInfo;
-import org.opends.server.types.DN;
-import org.opends.server.types.Modification;
-import org.opends.server.types.ModificationType;
-import org.opends.server.types.RDN;
-import org.opends.server.types.SearchFilter;
-import org.opends.server.types.SearchScope;
-
-import static org.testng.Assert.*;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
 
 
 
@@ -1177,8 +1173,8 @@
 
     // Try to add the entry.  If this fails with the proxy control, then add it
     // with a root connection so we can do other things with it.
-    AddOperation addOperation =
-         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           controls, e.getDN(), e.getObjectClasses(),
                           e.getUserAttributes(), e.getOperationalAttributes());
     addOperation.run();
@@ -1200,8 +1196,8 @@
     mods.add(new Modification(ModificationType.REPLACE,
                               new Attribute("description", "foo")));
 
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              controls, e.getDN(), mods);
     modifyOperation.run();
 
@@ -1239,8 +1235,8 @@
 
     // Try to delete the operation.  If this fails, then delete it with a root
     // connection so it gets cleaned up.
-    DeleteOperation deleteOperation =
-         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    DeleteOperationBasis deleteOperation =
+         new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              controls, newEntryDN);
     deleteOperation.run();
 
@@ -1255,8 +1251,8 @@
 
       InternalClientConnection rootConnection =
            InternalClientConnection.getRootConnection();
-      deleteOperation = rootConnection.processDelete(newEntryDN);
-      assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+      DeleteOperation delOp = rootConnection.processDelete(newEntryDN);
+      assertEquals(delOp.getResultCode(), ResultCode.SUCCESS);
     }
   }
 
@@ -1373,8 +1369,8 @@
     // Try to add the entry.  If this fails with the proxy control, then add it
     // with a root connection so we can do other things with it.
     DN authDN = conn.getAuthenticationInfo().getAuthenticationDN();
-    AddOperation addOperation =
-         new AddOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    AddOperationBasis addOperation =
+         new AddOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                           controls, e.getDN(), e.getObjectClasses(),
                           e.getUserAttributes(), e.getOperationalAttributes());
     addOperation.run();
@@ -1398,8 +1394,8 @@
     mods.add(new Modification(ModificationType.REPLACE,
                               new Attribute("description", "foo")));
 
-    ModifyOperation modifyOperation =
-         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              controls, e.getDN(), mods);
     modifyOperation.run();
 
@@ -1441,8 +1437,8 @@
 
     // Try to delete the operation.  If this fails, then delete it with a root
     // connection so it gets cleaned up.
-    DeleteOperation deleteOperation =
-         new DeleteOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
+    DeleteOperationBasis deleteOperation =
+         new DeleteOperationBasis(conn, conn.nextOperationID(), conn.nextMessageID(),
                              controls, newEntryDN);
     deleteOperation.run();
 
@@ -1459,8 +1455,8 @@
 
       InternalClientConnection rootConnection =
            InternalClientConnection.getRootConnection();
-      deleteOperation = rootConnection.processDelete(newEntryDN);
-      assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+      DeleteOperation delOp = rootConnection.processDelete(newEntryDN);
+      assertEquals(delOp.getResultCode(), ResultCode.SUCCESS);
     }
   }
 

--
Gitblit v1.10.0