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

jarnou
03.29.2007 fbda6e0892dcfcc8dd43d21f6fb134aabb8d0cac
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.
25 files added
79 files modified
26960 ■■■■ changed files
opends/src/server/org/opends/server/api/AccessControlHandler.java 26 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/ClientConnection.java 35 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/SynchronizationProvider.java 30 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/WorkQueue.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java 19 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java 19 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java 5 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/JMXMBean.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AbandonOperation.java 86 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AddOperation.java 2516 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AddOperationBasis.java 1024 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AddOperationWrapper.java 653 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/BindOperation.java 2244 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/BindOperationBasis.java 959 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/BindOperationWrapper.java 691 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/CompareOperation.java 81 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DefaultAccessControlProvider.java 18 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DeleteOperation.java 1383 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DeleteOperationBasis.java 671 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DeleteOperationWrapper.java 569 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 122 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ExtendedOperation.java 79 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/GroupManager.java 6 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyDNOperation.java 83 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyOperation.java 2931 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyOperationBasis.java 773 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyOperationWrapper.java 622 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/NetworkGroup.java 407 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/NetworkGroupCriteria.java 46 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/NetworkGroupNamingContexts.java 157 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/NetworkGroupPolicy.java 46 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PersistentSearch.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PluginConfigManager.java 232 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/RootDseWorkflowTopology.java 175 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SearchOperation.java 2310 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SearchOperationBasis.java 1759 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SearchOperationWrapper.java 903 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/UnbindOperation.java 68 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/Workflow.java 73 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/WorkflowImpl.java 127 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/WorkflowResultCode.java 249 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/WorkflowTopology.java 135 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/WorkflowTopologyNode.java 535 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/StaticGroup.java 10 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java 35 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/TraditionalWorkerThread.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java 19 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 19 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java 107 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java 25 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java 30 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java 87 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/Historical.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java 23 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java 7 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/AddMsg.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ModifyMsg.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AbstractOperation.java 691 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Operation.java 551 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/LeafWorkflowElement.java 40 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/WorkflowElement.java 81 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java 10 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java 14 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java 10 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ServerSideSortControlTestCase.java 30 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java 36 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/AbandonOperationTestCase.java 20 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/AddOperationTestCase.java 41 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java 87 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java 64 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java 130 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/NetworkGroupTest.java 470 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java 4 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/WorkflowTopologyTest.java 938 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java 14 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java 10 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java 11 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java 13 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java 6 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java 5 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LengthBasedPasswordValidatorTestCase.java 18 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidatorTestCase.java 34 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SimilarityBasedPasswordValidatorTestCase.java 10 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidatorTestCase.java 34 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java 27 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java 9 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxTestCase.java 4 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/InitOnLineTest.java 25 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ProtocolWindowTest.java 7 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReSyncTest.java 8 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java 41 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java 5 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/StressTest.java 6 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java 62 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java 7 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentServerStateTest.java 5 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentStateTest.java 5 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java 21 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/UpdateComparatorTest.java 3 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java 62 ●●●● patch | view | raw | blame | history
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,7 +194,8 @@
   *          the access control configuration, or <CODE>false</CODE>
   *          if not.
   */
  public abstract boolean maySend(SearchOperation searchOperation,
  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,7 +232,8 @@
   *         the access control configuration, or <CODE>false</CODE>
   *         if not.
   */
  public abstract boolean maySend(SearchOperation searchOperation,
  public abstract boolean maySend(
                          SearchOperation searchOperation,
                               SearchResultReference searchReference);
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;
  }
}
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;
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;
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
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;
  }
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;
    }
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())
            {
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);
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(")");
  }
}
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(")");
  }
}
opends/src/server/org/opends/server/core/AddOperationBasis.java
New file
@@ -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;
  }
}
opends/src/server/org/opends/server/core/AddOperationWrapper.java
New file
@@ -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);
  }
}
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(")");
  }
}
opends/src/server/org/opends/server/core/BindOperationBasis.java
New file
@@ -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);
      }
    }
  }
}
opends/src/server/org/opends/server/core/BindOperationWrapper.java
New file
@@ -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();
  }
}
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(")");
  }
}
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;
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;
  }
  /**
   * {@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;
  }
  public abstract void setProxiedAuthorizationDN(DN proxiedAuthorizationDN);
  /**
   * {@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(")");
  }
}
opends/src/server/org/opends/server/core/DeleteOperationBasis.java
New file
@@ -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;
  }
}
opends/src/server/org/opends/server/core/DeleteOperationWrapper.java
New file
@@ -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);
  }
}
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
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(")");
  }
}
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)
        {
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(")");
  }
}
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;
  }
  /**
   * {@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;
  }
  public abstract void setProxiedAuthorizationDN(DN proxiedAuthorizationDN);
  /**
   * {@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(")");
  }
}
opends/src/server/org/opends/server/core/ModifyOperationBasis.java
New file
@@ -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;
  }
}
opends/src/server/org/opends/server/core/ModifyOperationWrapper.java
New file
@@ -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);
  }
}
opends/src/server/org/opends/server/core/NetworkGroup.java
New file
@@ -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;
  }
}
opends/src/server/org/opends/server/core/NetworkGroupCriteria.java
New file
@@ -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.
  }
}
opends/src/server/org/opends/server/core/NetworkGroupNamingContexts.java
New file
@@ -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;
  }
}
opends/src/server/org/opends/server/core/NetworkGroupPolicy.java
New file
@@ -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.
  }
}
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.
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,8 +4563,6 @@
    return result;
  }
  /**
   * Invokes the set of search result entry plugins that have been configured
   * in the Directory Server.
@@ -4554,7 +4574,7 @@
   * @return  The result of processing the search result entry plugins.
   */
  public SearchEntryPluginResult invokeSearchResultEntryPlugins(
                                      SearchOperation searchOperation,
                                 LocalBackendSearchOperation searchOperation,
                                      SearchResultEntry searchEntry)
  {
    SearchEntryPluginResult result = null;
@@ -4613,7 +4633,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(
                                      SearchOperationBasis 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 reference plugins that have been
@@ -4626,7 +4714,77 @@
   * @return  The result of processing the search result reference plugins.
   */
  public SearchReferencePluginResult invokeSearchResultReferencePlugins(
                                          SearchOperation searchOperation,
                                   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
   * 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(
                                          SearchOperationBasis searchOperation,
                                          SearchResultReference searchReference)
  {
    SearchReferencePluginResult result = null;
opends/src/server/org/opends/server/core/RootDseWorkflowTopology.java
New file
@@ -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;
  }
}
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(")");
  }
}
Diff truncated after the above file
opends/src/server/org/opends/server/core/SearchOperationBasis.java opends/src/server/org/opends/server/core/SearchOperationWrapper.java opends/src/server/org/opends/server/core/UnbindOperation.java opends/src/server/org/opends/server/core/Workflow.java opends/src/server/org/opends/server/core/WorkflowImpl.java opends/src/server/org/opends/server/core/WorkflowResultCode.java opends/src/server/org/opends/server/core/WorkflowTopology.java opends/src/server/org/opends/server/core/WorkflowTopologyNode.java opends/src/server/org/opends/server/extensions/StaticGroup.java opends/src/server/org/opends/server/extensions/TraditionalWorkQueue.java opends/src/server/org/opends/server/extensions/TraditionalWorkerThread.java opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java opends/src/server/org/opends/server/messages/CoreMessages.java opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java opends/src/server/org/opends/server/protocols/internal/InternalSearchOperation.java opends/src/server/org/opends/server/protocols/jmx/JmxClientConnection.java opends/src/server/org/opends/server/protocols/jmx/RmiAuthenticator.java opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java opends/src/server/org/opends/server/replication/plugin/Historical.java opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java opends/src/server/org/opends/server/replication/protocol/AddMsg.java opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java opends/src/server/org/opends/server/replication/protocol/ModifyMsg.java opends/src/server/org/opends/server/types/AbstractOperation.java opends/src/server/org/opends/server/types/Operation.java opends/src/server/org/opends/server/workflowelement/LeafWorkflowElement.java opends/src/server/org/opends/server/workflowelement/WorkflowElement.java opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ServerSideSortControlTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/controls/VLVControlTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/core/AbandonOperationTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/core/AddOperationTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/core/DeleteOperationTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/core/NetworkGroupTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/core/WorkflowTopologyTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AnonymousSASLMechanismHandlerTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LengthBasedPasswordValidatorTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidatorTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SimilarityBasedPasswordValidatorTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidatorTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalClientConnectionTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxConnectTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/jmx/JmxTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/InitOnLineTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ProtocolWindowTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReSyncTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/StressTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/ModifyConflictTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentServerStateTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/PersistentStateTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/UpdateComparatorTest.java opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java