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

matthew_swift
05.42.2009 22094368c2865dcfb6daf8366425212b721a4657
opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
@@ -22,17 +22,25 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 */
package org.opends.server.protocols.ldap;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.loggers.AccessLogger.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.types.OperationType.*;
import static org.opends.server.util.StaticUtils.*;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.security.cert.Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -44,7 +52,6 @@
import org.opends.messages.MessageBuilder;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConnectionHandler;
import org.opends.server.api.ConnectionSecurityProvider;
import org.opends.server.core.AbandonOperationBasis;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.core.BindOperationBasis;
@@ -60,15 +67,18 @@
import org.opends.server.core.SearchOperationBasis;
import org.opends.server.core.UnbindOperationBasis;
import org.opends.server.core.networkgroups.NetworkGroup;
import org.opends.server.extensions.NullConnectionSecurityProvider;
import org.opends.server.extensions.ConnectionSecurityProvider;
import org.opends.server.extensions.RedirectingByteChannel;
import org.opends.server.extensions.TLSByteChannel;
import org.opends.server.extensions.TLSCapableConnection;
import org.opends.server.extensions.TLSConnectionSecurityProvider;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.monitors.OperationMonitor;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.asn1.ASN1;
import org.opends.server.protocols.asn1.ASN1ByteChannelReader;
import org.opends.server.protocols.asn1.ASN1Writer;
import org.opends.server.types.AbstractOperation;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringBuilder;
import org.opends.server.types.CancelRequest;
import org.opends.server.types.CancelResult;
import org.opends.server.types.Control;
@@ -83,151 +93,113 @@
import org.opends.server.types.SearchResultReference;
import org.opends.server.util.TimeThread;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.loggers.AccessLogger.logDisconnect;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import static org.opends.server.types.OperationType.*;
/**
 * This class defines an LDAP client connection, which is a type of client
 * connection that will be accepted by an instance of the LDAP connection
 * handler and have its requests decoded by an LDAP request handler.
 * This class defines an LDAP client connection, which is a type of
 * client connection that will be accepted by an instance of the LDAP
 * connection handler and have its requests decoded by an LDAP request
 * handler.
 */
public class LDAPClientConnection
       extends ClientConnection
       implements TLSCapableConnection
public class LDAPClientConnection extends ClientConnection implements
    TLSCapableConnection
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The time that the last operation was completed.
  private AtomicLong lastCompletionTime;
  private final AtomicLong lastCompletionTime;
  // The next operation ID that should be used for this connection.
  private AtomicLong nextOperationID;
  private final AtomicLong nextOperationID;
  // The selector that may be used for write operations.
  private AtomicReference<Selector> writeSelector;
  private final AtomicReference<Selector> writeSelector;
  // Indicates whether the Directory Server believes this connection to be
  // valid and available for communication.
  // Indicates whether the Directory Server believes this connection to
  // be valid and available for communication.
  private boolean connectionValid;
  // Indicates whether this connection is about to be closed.  This will be used
  // to prevent accepting new requests while a disconnect is in progress.
  // Indicates whether this connection is about to be closed. This will
  // be used to prevent accepting new requests while a disconnect is in
  // progress.
  private boolean disconnectRequested;
  // Indicates whether the connection should keep statistics regarding the
  // operations that it is performing.
  private boolean keepStats;
  // The BER type for the ASN.1 element that is in the process of being read.
  private byte elementType;
  // The encoded value for the ASN.1 element that is in the process of being
  // read.
  private byte[] elementValue;
  // Indicates whether the connection should keep statistics regarding
  // the operations that it is performing.
  private final boolean keepStats;
  // The set of all operations currently in progress on this connection.
  private ConcurrentHashMap<Integer,Operation> operationsInProgress;
  private final ConcurrentHashMap<Integer, Operation> operationsInProgress;
  // The number of operations performed on this connection.
  // Used to compare with the resource limits of the network group.
  private long operationsPerformed;
  // Lock on the number of operations
  private Object operationsPerformedLock;
  // The connection security provider that was in use for the client connection
  // before switching to a TLS-based provider.
  private ConnectionSecurityProvider clearSecurityProvider;
  // The connection security provider for this client connection.
  private ConnectionSecurityProvider securityProvider;
  private final Object operationsPerformedLock;
  // The port on the client from which this connection originated.
  private int clientPort;
  private final int clientPort;
  // The number of bytes contained in the value for the ASN.1 element that is in
  // the process of being read.
  private int elementLength;
  // The number of bytes in the multi-byte length that are still needed to fully
  // decode the length of the ASN.1 element in process.
  private int elementLengthBytesNeeded;
  // The current state for the data read for the ASN.1 element in progress.
  private int elementReadState;
  // The number of bytes that have already been read for the ASN.1 element
  // value in progress.
  private int elementValueBytesRead;
  // The number of bytes that are still needed to fully decode the value of the
  // ASN.1 element in progress.
  private int elementValueBytesNeeded;
  // The LDAP version that the client is using to communicate with the server.
  // The LDAP version that the client is using to communicate with the
  // server.
  private int ldapVersion;
  // The port on the server to which this client has connected.
  private int serverPort;
  private final int serverPort;
  // The reference to the connection handler that accepted this connection.
  private LDAPConnectionHandler connectionHandler;
  // The reference to the connection handler that accepted this
  // connection.
  private final LDAPConnectionHandler connectionHandler;
  // The reference to the request handler with which this connection is
  // associated.
  private LDAPRequestHandler requestHandler;
  private final LDAPRequestHandler requestHandler;
  // The statistics tracker associated with this client connection.
  private LDAPStatistics statTracker;
  private final LDAPStatistics statTracker;
  // The connectionHandler statistic tracker.
  private LDAPStatistics parentTracker;
  private final LDAPStatistics parentTracker;
  // The connection ID assigned to this connection.
  private long connectionID;
  private final long connectionID;
  // The lock used to provide threadsafe access to the set of operations in
  // progress.
  private Object opsInProgressLock;
  // The lock used to provide threadsafe access to the set of operations
  // in progress.
  private final Object opsInProgressLock;
  // The lock used to provide threadsafe access when sending data to the client.
  private Object transmitLock;
  // The lock used to provide threadsafe access when sending data to the
  // client.
  private final Object transmitLock;
  // The socket channel with which this client connection is associated.
  private SocketChannel clientChannel;
  private final SocketChannel clientChannel;
  // The string representation of the address of the client.
  private String clientAddress;
  private final String clientAddress;
  // The name of the protocol that the client is using to communicate with the
  // server.
  private String protocol;
  // The name of the protocol that the client is using to communicate
  // with the server.
  private final String protocol;
  // The string representation of the address of the server to which the client
  // has connected.
  private String serverAddress;
  // The string representation of the address of the server to which the
  // client has connected.
  private final String serverAddress;
  // The TLS connection security provider that may be used for this connection
  // if StartTLS is requested.
  private TLSConnectionSecurityProvider tlsSecurityProvider;
  private final ThreadLocal<WriterBuffer> cachedBuffers;
  //The SASL connection provider used if confidentiality/integrity is negotiated
  //during a SASL bind (GSSAPI and DIGEST-MD5 only).
  private ConnectionSecurityProvider saslSecurityProvider;
  private ASN1ByteChannelReader asn1Reader;
  private final RedirectingByteChannel saslChannel;
  private final RedirectingByteChannel tlsChannel;
  private ConnectionSecurityProvider activeProvider = null;
  private ConnectionSecurityProvider tlsPendingProvider = null;
  private ConnectionSecurityProvider saslPendingProvider = null;
  // Statistics for the processed operations
  private OperationMonitor addMonitor;
@@ -241,64 +213,67 @@
  private OperationMonitor moddnMonitor;
  private OperationMonitor unbindMonitor;
  /**
   * This class wraps the byte string buffer and ASN1 writer.
   */
  private static class WriterBuffer
  {
    ASN1Writer writer;
    ByteStringBuilder buffer;
  }
  /**
   * Creates a new LDAP client connection with the provided information.
   *
   * @param  connectionHandler  The connection handler that accepted this
   *                            connection.
   * @param  clientChannel      The socket channel that may be used to
   *                            communicate with the client.
   * @param connectionHandler
   *          The connection handler that accepted this connection.
   * @param clientChannel
   *          The socket channel that may be used to communicate with
   *          the client.
   */
  public LDAPClientConnection(LDAPConnectionHandler connectionHandler,
                              SocketChannel clientChannel)
      SocketChannel clientChannel)
  {
    super();
    this.connectionHandler     = connectionHandler;
    if (connectionHandler.isAdminConnectionHandler()) {
    this.connectionHandler = connectionHandler;
    if (connectionHandler.isAdminConnectionHandler())
    {
      setNetworkGroup(NetworkGroup.getAdminNetworkGroup());
    }
    this.clientChannel         = clientChannel;
    this.securityProvider      = null;
    this.clearSecurityProvider = null;
    this.clientChannel = clientChannel;
    opsInProgressLock = new Object();
    transmitLock      = new Object();
    elementReadState         = ELEMENT_READ_STATE_NEED_TYPE;
    elementType              = 0x00;
    elementLength            = 0;
    elementLengthBytesNeeded = 0;
    elementValue             = null;
    elementValueBytesRead    = 0;
    elementValueBytesNeeded  = 0;
    ldapVersion          = 3;
    requestHandler       = null;
    lastCompletionTime   = new AtomicLong(TimeThread.getTime());
    nextOperationID      = new AtomicLong(0);
    connectionValid      = true;
    disconnectRequested  = false;
    operationsInProgress = new ConcurrentHashMap<Integer,Operation>();
    transmitLock = new Object();
    ldapVersion = 3;
    requestHandler = null;
    lastCompletionTime = new AtomicLong(TimeThread.getTime());
    nextOperationID = new AtomicLong(0);
    connectionValid = true;
    disconnectRequested = false;
    operationsInProgress = new ConcurrentHashMap<Integer, Operation>();
    operationsPerformed = 0;
    operationsPerformedLock = new Object();
    keepStats            = connectionHandler.keepStats();
    protocol             = "LDAP";
    writeSelector        = new AtomicReference<Selector>();
    clientAddress = clientChannel.socket().getInetAddress().getHostAddress();
    clientPort    = clientChannel.socket().getPort();
    serverAddress = clientChannel.socket().getLocalAddress().getHostAddress();
    serverPort    = clientChannel.socket().getLocalPort();
    keepStats = connectionHandler.keepStats();
    protocol = "LDAP";
    writeSelector = new AtomicReference<Selector>();
    clientAddress =
        clientChannel.socket().getInetAddress().getHostAddress();
    clientPort = clientChannel.socket().getPort();
    serverAddress =
        clientChannel.socket().getLocalAddress().getHostAddress();
    serverPort = clientChannel.socket().getLocalPort();
    parentTracker = connectionHandler.getStatTracker();
    String         instanceName  = parentTracker.getMonitorInstanceName() +
                                   " for " + toString();
    String instanceName =
        parentTracker.getMonitorInstanceName() + " for " + toString();
    this.initializeOperationMonitors();
    statTracker = new LDAPStatistics(connectionHandler,
            instanceName, parentTracker);
    statTracker =
        new LDAPStatistics(connectionHandler, instanceName,
            parentTracker);
    if (keepStats)
    {
@@ -309,8 +284,16 @@
    if (connectionID < 0)
    {
      disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true,
                 ERR_LDAP_CONNHANDLER_REJECTED_BY_SERVER.get());
          ERR_LDAP_CONNHANDLER_REJECTED_BY_SERVER.get());
    }
    cachedBuffers = new ThreadLocal<WriterBuffer>();
    tlsChannel =
        RedirectingByteChannel.getRedirectingByteChannel(clientChannel);
    saslChannel =
        RedirectingByteChannel.getRedirectingByteChannel(tlsChannel);
    this.asn1Reader =
        ASN1.getReader(saslChannel, 4096, connectionHandler
            .getMaxRequestSize());
  }
@@ -318,8 +301,9 @@
  /**
   * Retrieves the connection ID assigned to this connection.
   *
   * @return  The connection ID assigned to this connection.
   * @return The connection ID assigned to this connection.
   */
  @Override
  public long getConnectionID()
  {
    return connectionID;
@@ -328,10 +312,13 @@
  /**
   * Retrieves the connection handler that accepted this client connection.
   * Retrieves the connection handler that accepted this client
   * connection.
   *
   * @return  The connection handler that accepted this client connection.
   * @return The connection handler that accepted this client
   *         connection.
   */
  @Override
  public ConnectionHandler<?> getConnectionHandler()
  {
    return connectionHandler;
@@ -340,11 +327,12 @@
  /**
   * Retrieves the request handler that will read requests for this client
   * connection.
   * Retrieves the request handler that will read requests for this
   * client connection.
   *
   * @return  The request handler that will read requests for this client
   *          connection, or <CODE>null</CODE> if none has been assigned yet.
   * @return The request handler that will read requests for this client
   *         connection, or <CODE>null</CODE> if none has been assigned
   *         yet.
   */
  public LDAPRequestHandler getRequestHandler()
  {
@@ -354,25 +342,11 @@
  /**
   * Specifies the request handler that will read requests for this client
   * connection.
   * Retrieves the socket channel that can be used to communicate with
   * the client.
   *
   * @param  requestHandler  The request handler that will read requests for
   *                         this client connection.
   */
  public void setRequestHandler(LDAPRequestHandler requestHandler)
  {
    this.requestHandler = requestHandler;
  }
  /**
   * Retrieves the socket channel that can be used to communicate with the
   * client.
   *
   * @return  The socket channel that can be used to communicate with the
   *          client.
   * @return The socket channel that can be used to communicate with the
   *         client.
   */
  public SocketChannel getSocketChannel()
  {
@@ -382,12 +356,13 @@
  /**
   * Retrieves the protocol that the client is using to communicate with the
   * Directory Server.
   * Retrieves the protocol that the client is using to communicate with
   * the Directory Server.
   *
   * @return  The protocol that the client is using to communicate with the
   *          Directory Server.
   * @return The protocol that the client is using to communicate with
   *         the Directory Server.
   */
  @Override
  public String getProtocol()
  {
    return protocol;
@@ -398,8 +373,9 @@
  /**
   * Retrieves a string representation of the address of the client.
   *
   * @return  A string representation of the address of the client.
   * @return A string representation of the address of the client.
   */
  @Override
  public String getClientAddress()
  {
    return clientAddress;
@@ -410,8 +386,9 @@
  /**
   * Retrieves the port number for this connection on the client system.
   *
   * @return  The port number for this connection on the client system.
   * @return The port number for this connection on the client system.
   */
  @Override
  public int getClientPort()
  {
    return clientPort;
@@ -420,12 +397,13 @@
  /**
   * Retrieves a string representation of the address on the server to which the
   * client connected.
   * Retrieves a string representation of the address on the server to
   * which the client connected.
   *
   * @return  A string representation of the address on the server to which the
   *          client connected.
   * @return A string representation of the address on the server to
   *         which the client connected.
   */
  @Override
  public String getServerAddress()
  {
    return serverAddress;
@@ -436,8 +414,9 @@
  /**
   * Retrieves the port number for this connection on the server system.
   *
   * @return  The port number for this connection on the server system.
   * @return The port number for this connection on the server system.
   */
  @Override
  public int getServerPort()
  {
    return serverPort;
@@ -446,13 +425,14 @@
  /**
   * Retrieves the <CODE>java.net.InetAddress</CODE> associated with the remote
   * client system.
   * Retrieves the <CODE>java.net.InetAddress</CODE> associated with the
   * remote client system.
   *
   * @return  The <CODE>java.net.InetAddress</CODE> associated with the remote
   *          client system.  It may be <CODE>null</CODE> if the client is not
   *          connected over an IP-based connection.
   * @return The <CODE>java.net.InetAddress</CODE> associated with the
   *         remote client system. It may be <CODE>null</CODE> if the
   *         client is not connected over an IP-based connection.
   */
  @Override
  public InetAddress getRemoteAddress()
  {
    return clientChannel.socket().getInetAddress();
@@ -461,14 +441,15 @@
  /**
   * Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory Server
   * system to which the client has established the connection.
   * Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory
   * Server system to which the client has established the connection.
   *
   * @return  The <CODE>java.net.InetAddress</CODE> for the Directory Server
   *          system to which the client has established the connection.  It may
   *          be <CODE>null</CODE> if the client is not connected over an
   *          IP-based connection.
   * @return The <CODE>java.net.InetAddress</CODE> for the Directory
   *         Server system to which the client has established the
   *         connection. It may be <CODE>null</CODE> if the client is
   *         not connected over an IP-based connection.
   */
  @Override
  public InetAddress getLocalAddress()
  {
    return clientChannel.socket().getLocalAddress();
@@ -477,92 +458,34 @@
  /**
   * Indicates whether this client connection is currently using a secure
   * mechanism to communicate with the server.  Note that this may change over
   * time based on operations performed by the client or server (e.g., it may go
   * from <CODE>false</CODE> to <CODE>true</CODE> if the client uses the
   * StartTLS extended operation).
   * Indicates whether this client connection is currently using a
   * secure mechanism to communicate with the server. Note that this may
   * change over time based on operations performed by the client or
   * server (e.g., it may go from <CODE>false</CODE> to
   * <CODE>true</CODE> if the client uses the StartTLS extended
   * operation).
   *
   * @return  <CODE>true</CODE> if the client connection is currently using a
   *          secure mechanism to communicate with the server, or
   *          <CODE>false</CODE> if not.
   * @return <CODE>true</CODE> if the client connection is currently
   *         using a secure mechanism to communicate with the server, or
   *         <CODE>false</CODE> if not.
   */
  @Override
  public boolean isSecure()
  {
    return securityProvider.isSecure();
  }
  /**
   * Retrieves the connection security provider for this client connection.
   *
   * @return  The connection security provider for this client connection.
   */
  public ConnectionSecurityProvider getConnectionSecurityProvider()
  {
      if(saslSecurityProvider != null && saslSecurityProvider.isActive())
          securityProvider =  saslSecurityProvider;
      return securityProvider;
  }
  /**
   * Set the security provider to be used to process SASL (DIGEST-MD5, GSSAPI)
   * confidentiality/integrity messages.
   *
   * @param secProvider The security provider to use.
   */
    public void
    setSASLConnectionSecurityProvider(ConnectionSecurityProvider secProvider) {
        saslSecurityProvider = secProvider;
    }
  /**
   * Specifies the connection security provider for this client connection.
   *
   * @param  securityProvider  The connection security provider to use for
   *                           communication on this client connection.
   */
  public void setConnectionSecurityProvider(ConnectionSecurityProvider
                                                 securityProvider)
  {
    this.securityProvider = securityProvider;
    if (securityProvider.isSecure())
    {
      protocol = "LDAP+" + securityProvider.getSecurityMechanismName();
    }
    if (activeProvider != null)
      return activeProvider.isSecure();
    else
    {
      protocol = "LDAP";
    }
      return false;
  }
  /**
   * Retrieves the human-readable name of the security mechanism that is used to
   * protect communication with this client.
   * Retrieves the next operation ID that should be used for this
   * connection.
   *
   * @return  The human-readable name of the security mechanism that is used to
   *          protect communication with this client, or <CODE>null</CODE> if no
   *          security is in place.
   */
  public String getSecurityMechanism()
  {
    return securityProvider.getSecurityMechanismName();
  }
  /**
   * Retrieves the next operation ID that should be used for this connection.
   *
   * @return  The next operation ID that should be used for this connection.
   * @return The next operation ID that should be used for this
   *         connection.
   */
  public long nextOperationID()
  {
@@ -572,57 +495,56 @@
  /**
   * Sends a response to the client based on the information in the provided
   * operation.
   * Sends a response to the client based on the information in the
   * provided operation.
   *
   * @param  operation  The operation for which to send the response.
   * @param operation
   *          The operation for which to send the response.
   */
  @Override
  public void sendResponse(Operation operation)
  {
    // Since this is the final response for this operation, we can go ahead and
    // remove it from the "operations in progress" list.  It can't be canceled
    // after this point, and this will avoid potential race conditions in which
    // the client immediately sends another request with the same message ID as
    // was used for this operation.
    // Since this is the final response for this operation, we can go
    // ahead and remove it from the "operations in progress" list. It
    // can't be canceled after this point, and this will avoid potential
    // race conditions in which the client immediately sends another
    // request with the same message ID as was used for this operation.
    removeOperationInProgress(operation.getMessageID());
    LDAPMessage message = operationToResponseLDAPMessage(operation);
    if (message != null)
    {
      sendLDAPMessage(securityProvider, message);
      sendLDAPMessage(message);
    }
  }
  /**
   * Retrieves an LDAPMessage containing a response generated from the provided
   * operation.
   * Retrieves an LDAPMessage containing a response generated from the
   * provided operation.
   *
   * @param  operation  The operation to use to generate the response
   *                    LDAPMessage.
   *
   * @return  An LDAPMessage containing a response generated from the provided
   *          operation.
   * @param operation
   *          The operation to use to generate the response LDAPMessage.
   * @return An LDAPMessage containing a response generated from the
   *         provided operation.
   */
  private LDAPMessage operationToResponseLDAPMessage(Operation operation)
  {
    ResultCode resultCode = operation.getResultCode();
    if (resultCode == null)
    {
      // This must mean that the operation has either not yet completed or that
      // it completed without a result for some reason.  In any case, log a
      // message and set the response to "operations error".
      logError(ERR_LDAP_CLIENT_SEND_RESPONSE_NO_RESULT_CODE.
          get(operation.getOperationType().toString(),
              operation.getConnectionID(), operation.getOperationID()));
      // This must mean that the operation has either not yet completed
      // or that it completed without a result for some reason. In any
      // case, log a message and set the response to "operations error".
      logError(ERR_LDAP_CLIENT_SEND_RESPONSE_NO_RESULT_CODE.get(
          operation.getOperationType().toString(), operation
              .getConnectionID(), operation.getOperationID()));
      resultCode = DirectoryServer.getServerErrorResultCode();
    }
    MessageBuilder errorMessage = operation.getErrorMessage();
    DN             matchedDN    = operation.getMatchedDN();
    DN matchedDN = operation.getMatchedDN();
    // Referrals are not allowed for LDAPv2 clients.
    List<String> referralURLs;
@@ -637,7 +559,7 @@
      }
      List<String> opReferrals = operation.getReferralURLs();
      if ((opReferrals != null) && (! opReferrals.isEmpty()))
      if ((opReferrals != null) && (!opReferrals.isEmpty()))
      {
        StringBuilder referralsStr = new StringBuilder();
        Iterator<String> iterator = opReferrals.iterator();
@@ -649,8 +571,8 @@
          referralsStr.append(iterator.next());
        }
        errorMessage.append(ERR_LDAPV2_REFERRALS_OMITTED.get(
                String.valueOf(referralsStr)));
        errorMessage.append(ERR_LDAPV2_REFERRALS_OMITTED.get(String
            .valueOf(referralsStr)));
      }
    }
    else
@@ -661,94 +583,82 @@
    ProtocolOp protocolOp;
    switch (operation.getOperationType())
    {
      case ADD:
        protocolOp = new AddResponseProtocolOp(resultCode.getIntValue(),
                                               errorMessage.toMessage(),
                                               matchedDN, referralURLs);
        break;
      case BIND:
        ASN1OctetString serverSASLCredentials =
             ((BindOperationBasis) operation).getServerSASLCredentials();
        protocolOp = new BindResponseProtocolOp(resultCode.getIntValue(),
                              errorMessage.toMessage(), matchedDN,
                              referralURLs, serverSASLCredentials);
        break;
      case COMPARE:
        protocolOp = new CompareResponseProtocolOp(resultCode.getIntValue(),
                                                   errorMessage.toMessage(),
                                                   matchedDN, referralURLs);
        break;
      case DELETE:
        protocolOp = new DeleteResponseProtocolOp(resultCode.getIntValue(),
                                                  errorMessage.toMessage(),
                                                  matchedDN, referralURLs);
        break;
      case EXTENDED:
        // If this an LDAPv2 client, then we can't send this.
        if (ldapVersion == 2)
        {
          logError(ERR_LDAPV2_SKIPPING_EXTENDED_RESPONSE.get(
              getConnectionID(), operation.getOperationID(),
                  String.valueOf(operation)));
          return null;
        }
        ExtendedOperationBasis extOp = (ExtendedOperationBasis) operation;
        protocolOp = new ExtendedResponseProtocolOp(resultCode.getIntValue(),
                              errorMessage.toMessage(), matchedDN, referralURLs,
                              extOp.getResponseOID(), extOp.getResponseValue());
        break;
      case MODIFY:
        protocolOp = new ModifyResponseProtocolOp(resultCode.getIntValue(),
                                                  errorMessage.toMessage(),
                                                  matchedDN, referralURLs);
        break;
      case MODIFY_DN:
        protocolOp = new ModifyDNResponseProtocolOp(resultCode.getIntValue(),
                                                    errorMessage.toMessage(),
                                                    matchedDN, referralURLs);
        break;
      case SEARCH:
        protocolOp = new SearchResultDoneProtocolOp(resultCode.getIntValue(),
                                                    errorMessage.toMessage(),
                                                    matchedDN, referralURLs);
        break;
      default:
        // This must be a type of operation that doesn't have a response.  This
        // shouldn't happen, so log a message and return.
        logError(ERR_LDAP_CLIENT_SEND_RESPONSE_INVALID_OP.get(
                String.valueOf(operation.getOperationType()),
                getConnectionID(),
                operation.getOperationID(),
                String.valueOf(operation)));
    case ADD:
      protocolOp =
          new AddResponseProtocolOp(resultCode.getIntValue(),
              errorMessage.toMessage(), matchedDN, referralURLs);
      break;
    case BIND:
      ByteString serverSASLCredentials =
          ((BindOperationBasis) operation).getServerSASLCredentials();
      protocolOp =
          new BindResponseProtocolOp(resultCode.getIntValue(),
              errorMessage.toMessage(), matchedDN, referralURLs,
              serverSASLCredentials);
      break;
    case COMPARE:
      protocolOp =
          new CompareResponseProtocolOp(resultCode.getIntValue(),
              errorMessage.toMessage(), matchedDN, referralURLs);
      break;
    case DELETE:
      protocolOp =
          new DeleteResponseProtocolOp(resultCode.getIntValue(),
              errorMessage.toMessage(), matchedDN, referralURLs);
      break;
    case EXTENDED:
      // If this an LDAPv2 client, then we can't send this.
      if (ldapVersion == 2)
      {
        logError(ERR_LDAPV2_SKIPPING_EXTENDED_RESPONSE.get(
            getConnectionID(), operation.getOperationID(), String
                .valueOf(operation)));
        return null;
      }
      ExtendedOperationBasis extOp = (ExtendedOperationBasis) operation;
      protocolOp =
          new ExtendedResponseProtocolOp(resultCode.getIntValue(),
              errorMessage.toMessage(), matchedDN, referralURLs, extOp
                  .getResponseOID(), extOp.getResponseValue());
      break;
    case MODIFY:
      protocolOp =
          new ModifyResponseProtocolOp(resultCode.getIntValue(),
              errorMessage.toMessage(), matchedDN, referralURLs);
      break;
    case MODIFY_DN:
      protocolOp =
          new ModifyDNResponseProtocolOp(resultCode.getIntValue(),
              errorMessage.toMessage(), matchedDN, referralURLs);
      break;
    case SEARCH:
      protocolOp =
          new SearchResultDoneProtocolOp(resultCode.getIntValue(),
              errorMessage.toMessage(), matchedDN, referralURLs);
      break;
    default:
      // This must be a type of operation that doesn't have a response.
      // This shouldn't happen, so log a message and return.
      logError(ERR_LDAP_CLIENT_SEND_RESPONSE_INVALID_OP.get(String
          .valueOf(operation.getOperationType()), getConnectionID(),
          operation.getOperationID(), String.valueOf(operation)));
      return null;
    }
    // Controls are not allowed for LDAPv2 clients.
    ArrayList<LDAPControl> controls;
    List<Control> controls;
    if (ldapVersion == 2)
    {
      controls = null;
    }
    else
    {
      List<Control> responseControls = operation.getResponseControls();
      if ((responseControls == null) || responseControls.isEmpty())
      {
        controls = null;
      }
      else
      {
        controls = new ArrayList<LDAPControl>(responseControls.size());
        for (Control c : responseControls)
        {
          controls.add(new LDAPControl(c));
        }
      }
      controls = operation.getResponseControls();
    }
    return new LDAPMessage(operation.getMessageID(), protocolOp, controls);
    return new LDAPMessage(operation.getMessageID(), protocolOp,
        controls);
  }
@@ -756,34 +666,20 @@
  /**
   * Sends the provided search result entry to the client.
   *
   * @param  searchOperation  The search operation with which the entry is
   *                          associated.
   * @param  searchEntry      The search result entry to be sent to the client.
   * @param searchOperation
   *          The search operation with which the entry is associated.
   * @param searchEntry
   *          The search result entry to be sent to the client.
   */
  @Override
  public void sendSearchEntry(SearchOperation searchOperation,
                              SearchResultEntry searchEntry)
      SearchResultEntry searchEntry)
  {
    SearchResultEntryProtocolOp protocolOp =
         new SearchResultEntryProtocolOp(searchEntry,ldapVersion);
        new SearchResultEntryProtocolOp(searchEntry, ldapVersion);
    List<Control> entryControls = searchEntry.getControls();
    ArrayList<LDAPControl> controls;
    if ((entryControls == null) || entryControls.isEmpty())
    {
      controls = null;
    }
    else
    {
      controls = new ArrayList<LDAPControl>(entryControls.size());
      for (Control c : entryControls)
      {
        controls.add(new LDAPControl(c));
      }
    }
    sendLDAPMessage(securityProvider,
                    new LDAPMessage(searchOperation.getMessageID(), protocolOp,
                                    controls));
    sendLDAPMessage(new LDAPMessage(searchOperation.getMessageID(),
        protocolOp, searchEntry.getControls()));
  }
@@ -791,92 +687,69 @@
  /**
   * Sends the provided search result reference to the client.
   *
   * @param  searchOperation  The search operation with which the reference is
   *                          associated.
   * @param  searchReference  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.
   * @param searchOperation
   *          The search operation with which the reference is
   *          associated.
   * @param searchReference
   *          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.
   */
  @Override
  public boolean sendSearchReference(SearchOperation searchOperation,
                                     SearchResultReference searchReference)
      SearchResultReference searchReference)
  {
    // Make sure this is not an LDAPv2 client.  If it is, then they can't see
    // referrals so we'll not send anything.  Also, throw an exception so that
    // the core server will know not to try sending any more referrals to this
    // client for the rest of the operation.
    // Make sure this is not an LDAPv2 client. If it is, then they can't
    // see referrals so we'll not send anything. Also, throw an
    // exception so that the core server will know not to try sending
    // any more referrals to this client for the rest of the operation.
    if (ldapVersion == 2)
    {
      Message message = ERR_LDAPV2_SKIPPING_SEARCH_REFERENCE.
          get(getConnectionID(), searchOperation.getOperationID(),
              String.valueOf(searchReference));
      Message message =
          ERR_LDAPV2_SKIPPING_SEARCH_REFERENCE.get(getConnectionID(),
              searchOperation.getOperationID(), String
                  .valueOf(searchReference));
      logError(message);
      return false;
    }
    SearchResultReferenceProtocolOp protocolOp =
         new SearchResultReferenceProtocolOp(searchReference);
        new SearchResultReferenceProtocolOp(searchReference);
    List<Control> referenceControls = searchReference.getControls();
    ArrayList<LDAPControl> controls;
    if ((referenceControls == null) || referenceControls.isEmpty())
    {
      controls = null;
    }
    else
    {
      controls = new ArrayList<LDAPControl>(referenceControls.size());
      for (Control c : referenceControls)
      {
        controls.add(new LDAPControl(c));
      }
    }
    sendLDAPMessage(securityProvider,
                    new LDAPMessage(searchOperation.getMessageID(), protocolOp,
                                    controls));
    sendLDAPMessage(new LDAPMessage(searchOperation.getMessageID(),
        protocolOp, searchReference.getControls()));
    return true;
  }
  /**
   * Sends the provided intermediate response message to the client.
   *
   * @param  intermediateResponse  The intermediate response message to be sent.
   *
   * @return  <CODE>true</CODE> if processing on the associated operation should
   *          continue, or <CODE>false</CODE> if not.
   * @param intermediateResponse
   *          The intermediate response message to be sent.
   * @return <CODE>true</CODE> if processing on the associated operation
   *         should continue, or <CODE>false</CODE> if not.
   */
  @Override
  protected boolean sendIntermediateResponseMessage(
                         IntermediateResponse intermediateResponse)
      IntermediateResponse intermediateResponse)
  {
    IntermediateResponseProtocolOp protocolOp =
         new IntermediateResponseProtocolOp(intermediateResponse.getOID(),
                                            intermediateResponse.getValue());
        new IntermediateResponseProtocolOp(intermediateResponse
            .getOID(), intermediateResponse.getValue());
    Operation operation = intermediateResponse.getOperation();
    List<Control> controls = intermediateResponse.getControls();
    ArrayList<LDAPControl> ldapControls =
         new ArrayList<LDAPControl>(controls.size());
    for (Control c : controls)
    {
      ldapControls.add(new LDAPControl(c));
    }
    LDAPMessage message =
        new LDAPMessage(operation.getMessageID(), protocolOp,
            intermediateResponse.getControls());
    sendLDAPMessage(message);
    LDAPMessage message = new LDAPMessage(operation.getMessageID(), protocolOp,
                                          ldapControls);
    sendLDAPMessage(securityProvider, message);
    // The only reason we shouldn't continue processing is if the connection is
    // closed.
    // The only reason we shouldn't continue processing is if the
    // connection is closed.
    return connectionValid;
  }
@@ -885,95 +758,92 @@
  /**
   * Sends the provided LDAP message to the client.
   *
   * @param  secProvider  The connection security provider to use to handle any
   *                      necessary security translation.
   * @param  message      The LDAP message to send to the client.
   * @param message
   *          The LDAP message to send to the client.
   */
  public void sendLDAPMessage(ConnectionSecurityProvider secProvider,
                              LDAPMessage message)
  public void sendLDAPMessage(LDAPMessage message)
  {
    ASN1Element messageElement = message.encode();
    ByteBuffer messageBuffer = ByteBuffer.wrap(messageElement.encode());
    // Make sure that we can only send one message at a time.  This locking will
    // not have any impact on the ability to read requests from the client.
    synchronized (transmitLock)
    // Get the buffer used by this thread.
    WriterBuffer writerBuffer = cachedBuffers.get();
    if (writerBuffer == null)
    {
      try
      writerBuffer = new WriterBuffer();
      // TODO SASLPhase2 maybe don't want to cache these
      if (isSecure())
      {
        try
        int appBufSize = activeProvider.getAppBufSize();
        writerBuffer.writer = ASN1.getWriter(saslChannel, appBufSize);
      }
      else
        writerBuffer.writer = ASN1.getWriter(saslChannel, 4096);
      cachedBuffers.set(writerBuffer);
    }
    try
    {
      // Make sure that we can only send one message at a time. This
      // locking will not have any impact on the ability to read
      // requests from the client.
      synchronized (transmitLock)
      {
        message.write(writerBuffer.writer);
        writerBuffer.writer.flush();
        if(debugEnabled())
        {
          int bytesWritten = messageBuffer.limit() - messageBuffer.position();
          if (! secProvider.writeData(messageBuffer))
          {
            return;
          }
          TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
              message.toString());
          TRACER.debugProtocolElement(DebugLogLevel.VERBOSE, message);
          TRACER.debugProtocolElement(DebugLogLevel.VERBOSE, messageElement);
          messageBuffer.rewind();
          if (debugEnabled())
          {
            TRACER.debugData(DebugLogLevel.VERBOSE, messageBuffer);
          }
          if (keepStats)
          {
            statTracker.updateMessageWritten(message, bytesWritten);
          }
          // TODO SASLPhase2 message buffer?
          // TRACER.debugData(DebugLogLevel.VERBOSE, messageBuffer);
        }
        catch (@Deprecated Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          // We were unable to send the message due to some other internal
          // problem.  Disconnect from the client and return.
          disconnect(DisconnectReason.SERVER_ERROR, true, null);
          return;
        if (keepStats)
        {
          // TODO SASLPhase2 hard-coded for now, flush probably needs to
          // return how many bytes were flushed.
          statTracker.updateMessageWritten(message, 4096);
        }
      }
      catch (Exception e)
      // writerBuffer.buffer.clear();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        // FIXME -- Log a message or something
        disconnect(DisconnectReason.SERVER_ERROR, true, null);
        return;
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      // FIXME -- Log a message or something
      disconnect(DisconnectReason.SERVER_ERROR, true, null);
      return;
    }
  }
  /**
   * Closes the connection to the client, optionally sending it a message
   * indicating the reason for the closure.  Note that the ability to send a
   * notice of disconnection may not be available for all protocols or under all
   * circumstances.
   * Closes the connection to the client, optionally sending it a
   * message indicating the reason for the closure. Note that the
   * ability to send a notice of disconnection may not be available for
   * all protocols or under all circumstances.
   *
   * @param  disconnectReason  The disconnect reason that provides the generic
   *                           cause for the disconnect.
   * @param  sendNotification  Indicates whether to try to provide notification
   *                           to the client that the connection will be closed.
   * @param  message           The message to include in the disconnect
   *                           notification response.  It may be
   *                           <CODE>null</CODE> if no message is to be sent.
   * @param disconnectReason
   *          The disconnect reason that provides the generic cause for
   *          the disconnect.
   * @param sendNotification
   *          Indicates whether to try to provide notification to the
   *          client that the connection will be closed.
   * @param message
   *          The message to include in the disconnect notification
   *          response. It may be <CODE>null</CODE> if no message is to
   *          be sent.
   */
  @Override
  public void disconnect(DisconnectReason disconnectReason,
                         boolean sendNotification,
                         Message message)
      boolean sendNotification, Message message)
  {
    // Set a flag indicating that the connection is being terminated so that no
    // new requests will be accepted.  Also cancel all operations in progress.
    // Set a flag indicating that the connection is being terminated so
    // that no new requests will be accepted. Also cancel all operations
    // in progress.
    synchronized (opsInProgressLock)
    {
      // If we are already in the middle of a disconnect, then don't
@@ -986,7 +856,6 @@
      disconnectRequested = true;
    }
    if (keepStats)
    {
      statTracker.updateDisconnect();
@@ -997,26 +866,25 @@
      DirectoryServer.connectionClosed(this);
    }
    // Indicate that this connection is no longer valid.
    connectionValid = false;
    if(message != null)
    if (message != null)
    {
      MessageBuilder msgBuilder = new MessageBuilder();
      msgBuilder.append(disconnectReason.getClosureMessage());
      msgBuilder.append(": ");
      msgBuilder.append(message);
      cancelAllOperations(new CancelRequest(true, msgBuilder.toMessage()));
      cancelAllOperations(new CancelRequest(true, msgBuilder
          .toMessage()));
    }
    else
    {
      cancelAllOperations(new CancelRequest(true,
          disconnectReason.getClosureMessage()));
      cancelAllOperations(new CancelRequest(true, disconnectReason
          .getClosureMessage()));
    }
    finalizeConnectionInternal();
    // If there is a write selector for this connection, then close it.
    Selector selector = writeSelector.get();
    if (selector != null)
@@ -1024,13 +892,16 @@
      try
      {
        selector.close();
      } catch (Exception e) {}
      }
      catch (Exception e)
      {
      }
    }
    // See if we should send a notification to the client.  If so, then
    // construct and send a notice of disconnection unsolicited response.
    // Note that we cannot send this notification to an LDAPv2 client.
    // See if we should send a notification to the client. If so, then
    // construct and send a notice of disconnection unsolicited
    // response. Note that we cannot send this notification to an LDAPv2
    // client.
    if (sendNotification && (ldapVersion != 2))
    {
      try
@@ -1038,70 +909,63 @@
        int resultCode;
        switch (disconnectReason)
        {
          case PROTOCOL_ERROR:
            resultCode = LDAPResultCode.PROTOCOL_ERROR;
            break;
          case SERVER_SHUTDOWN:
            resultCode = LDAPResultCode.UNAVAILABLE;
            break;
          case SERVER_ERROR:
            resultCode =
                 DirectoryServer.getServerErrorResultCode().getIntValue();
            break;
          case ADMIN_LIMIT_EXCEEDED:
          case IDLE_TIME_LIMIT_EXCEEDED:
          case MAX_REQUEST_SIZE_EXCEEDED:
          case IO_TIMEOUT:
            resultCode = LDAPResultCode.ADMIN_LIMIT_EXCEEDED;
            break;
          case CONNECTION_REJECTED:
            resultCode = LDAPResultCode.CONSTRAINT_VIOLATION;
            break;
          default:
            resultCode = LDAPResultCode.OTHER;
            break;
        case PROTOCOL_ERROR:
          resultCode = LDAPResultCode.PROTOCOL_ERROR;
          break;
        case SERVER_SHUTDOWN:
          resultCode = LDAPResultCode.UNAVAILABLE;
          break;
        case SERVER_ERROR:
          resultCode =
              DirectoryServer.getServerErrorResultCode().getIntValue();
          break;
        case ADMIN_LIMIT_EXCEEDED:
        case IDLE_TIME_LIMIT_EXCEEDED:
        case MAX_REQUEST_SIZE_EXCEEDED:
        case IO_TIMEOUT:
          resultCode = LDAPResultCode.ADMIN_LIMIT_EXCEEDED;
          break;
        case CONNECTION_REJECTED:
          resultCode = LDAPResultCode.CONSTRAINT_VIOLATION;
          break;
        default:
          resultCode = LDAPResultCode.OTHER;
          break;
        }
        Message errMsg;
        if (message == null)
        {
          errMsg = INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get();
          errMsg =
              INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get();
        }
        else
        {
          errMsg = message;
        }
        ExtendedResponseProtocolOp notificationOp =
             new ExtendedResponseProtocolOp(resultCode, errMsg, null, null,
                                            OID_NOTICE_OF_DISCONNECTION, null);
        byte[] messageBytes =
                    new LDAPMessage(0, notificationOp, null).encode().encode();
        ByteBuffer buffer = ByteBuffer.wrap(messageBytes);
        try
        {
          securityProvider.writeData(buffer);
        } catch (Exception e) {}
            new ExtendedResponseProtocolOp(resultCode, errMsg, null,
                null, OID_NOTICE_OF_DISCONNECTION, null);
        sendLDAPMessage(new LDAPMessage(0, notificationOp, null));
      }
      catch (Exception e)
      {
        // NYI -- Log a message indicating that we couldn't send the notice of
        // disconnection.
        // NYI -- Log a message indicating that we couldn't send the
        // notice of disconnection.
      }
    }
    // Close the connection to the client.
    try
    {
      securityProvider.disconnect(sendNotification);
      asn1Reader.close();
    }
    catch (Exception e)
    {
      // In general, we don't care about any exception that might be thrown
      // here.
      // In general, we don't care about any exception that might be
      // thrown here.
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
@@ -1114,29 +978,29 @@
    }
    catch (Exception e)
    {
      // In general, we don't care about any exception that might be thrown
      // here.
      // In general, we don't care about any exception that might be
      // thrown here.
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    // Remove the thread local buffers.
    cachedBuffers.remove();
    // NYI -- Deregister the client connection from any server components that
    // might know about it.
    // Log a disconnect message.
    logDisconnect(this, disconnectReason, message);
    try
    {
      PluginConfigManager pluginManager =
           DirectoryServer.getPluginConfigManager();
          DirectoryServer.getPluginConfigManager();
      pluginManager.invokePostDisconnectPlugins(this, disconnectReason,
              message);
          message);
    }
    catch (Exception e)
    {
@@ -1150,11 +1014,13 @@
  /**
   * Retrieves the set of operations in progress for this client connection.
   * This list must not be altered by any caller.
   * Retrieves the set of operations in progress for this client
   * connection. This list must not be altered by any caller.
   *
   * @return  The set of operations in progress for this client connection.
   * @return The set of operations in progress for this client
   *         connection.
   */
  @Override
  public Collection<Operation> getOperationsInProgress()
  {
    return operationsInProgress.values();
@@ -1165,11 +1031,12 @@
  /**
   * Retrieves the operation in progress with the specified message ID.
   *
   * @param  messageID  The message ID for the operation to retrieve.
   *
   * @return  The operation in progress with the specified message ID, or
   *          <CODE>null</CODE> if no such operation could be found.
   * @param messageID
   *          The message ID for the operation to retrieve.
   * @return The operation in progress with the specified message ID, or
   *         <CODE>null</CODE> if no such operation could be found.
   */
  @Override
  public Operation getOperationInProgress(int messageID)
  {
    return operationsInProgress.get(messageID);
@@ -1178,56 +1045,59 @@
  /**
   * Adds the provided operation to the set of operations in progress for this
   * client connection.
   * Adds the provided operation to the set of operations in progress
   * for this client connection.
   *
   * @param  operation  The operation to add to the set of operations in
   *                    progress for this client connection.
   *
   * @throws  DirectoryException  If the operation is not added for some reason
   *                              (e.g., the client already has reached the
   *                              maximum allowed concurrent requests).
   * @param operation
   *          The operation to add to the set of operations in progress
   *          for this client connection.
   * @throws DirectoryException
   *           If the operation is not added for some reason (e.g., the
   *           client already has reached the maximum allowed concurrent
   *           requests).
   */
  public void addOperationInProgress(AbstractOperation operation)
         throws DirectoryException
      throws DirectoryException
  {
    int messageID = operation.getMessageID();
    // We need to grab a lock to ensure that no one else can add operations to
    // the queue while we are performing some preliminary checks.
    // We need to grab a lock to ensure that no one else can add
    // operations to the queue while we are performing some preliminary
    // checks.
    synchronized (opsInProgressLock)
    {
      try
      {
        // If we're already in the process of disconnecting the client, then
        // reject the operation.
        // If we're already in the process of disconnecting the client,
        // then reject the operation.
        if (disconnectRequested)
        {
          Message message = WARN_LDAP_CLIENT_DISCONNECT_IN_PROGRESS.get();
          Message message =
              WARN_LDAP_CLIENT_DISCONNECT_IN_PROGRESS.get();
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                       message);
              message);
        }
        // See if there is already an operation in progress with the same
        // message ID.  If so, then we can't allow it.
        // See if there is already an operation in progress with the
        // same message ID. If so, then we can't allow it.
        Operation op = operationsInProgress.get(messageID);
        if (op != null)
        {
          Message message =
               WARN_LDAP_CLIENT_DUPLICATE_MESSAGE_ID.get(messageID);
          throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
              WARN_LDAP_CLIENT_DUPLICATE_MESSAGE_ID.get(messageID);
          throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
              message);
        }
        // Add the operation to the list of operations in progress for this
        // connection.
        // Add the operation to the list of operations in progress for
        // this connection.
        operationsInProgress.put(messageID, operation);
        // Try to add the operation to the work queue,
        // or run it synchronously (typically for the administration connector)
        connectionHandler.getQueueingStrategy().enqueueRequest(operation);
        // or run it synchronously (typically for the administration
        // connector)
        connectionHandler.getQueueingStrategy().enqueueRequest(
            operation);
      }
      catch (DirectoryException de)
      {
@@ -1250,8 +1120,8 @@
        Message message =
            WARN_LDAP_CLIENT_CANNOT_ENQUEUE.get(getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, e);
        throw new DirectoryException(DirectoryServer
            .getServerErrorResultCode(), message, e);
      }
    }
  }
@@ -1259,16 +1129,19 @@
  /**
   * Removes the provided operation from the set of operations in progress for
   * this client connection.  Note that this does not make any attempt to
   * cancel any processing that may already be in progress for the operation.
   * Removes the provided operation from the set of operations in
   * progress for this client connection. Note that this does not make
   * any attempt to cancel any processing that may already be in
   * progress for the operation.
   *
   * @param  messageID  The message ID of the operation to remove from the set
   *                    of operations in progress.
   *
   * @return  <CODE>true</CODE> if the operation was found and removed from the
   *          set of operations in progress, or <CODE>false</CODE> if not.
   * @param messageID
   *          The message ID of the operation to remove from the set of
   *          operations in progress.
   * @return <CODE>true</CODE> if the operation was found and removed
   *         from the set of operations in progress, or
   *         <CODE>false</CODE> if not.
   */
  @Override
  public boolean removeOperationInProgress(int messageID)
  {
    Operation operation = operationsInProgress.remove(messageID);
@@ -1288,15 +1161,17 @@
  /**
   * Attempts to cancel the specified operation.
   *
   * @param  messageID      The message ID of the operation to cancel.
   * @param  cancelRequest  An object providing additional information about how
   *                        the cancel should be processed.
   *
   * @return  A cancel result that either indicates that the cancel was
   *          successful or provides a reason that it was not.
   * @param messageID
   *          The message ID of the operation to cancel.
   * @param cancelRequest
   *          An object providing additional information about how the
   *          cancel should be processed.
   * @return A cancel result that either indicates that the cancel was
   *         successful or provides a reason that it was not.
   */
  @Override
  public CancelResult cancelOperation(int messageID,
                                      CancelRequest cancelRequest)
      CancelRequest cancelRequest)
  {
    Operation op = operationsInProgress.get(messageID);
    if (op == null)
@@ -1331,9 +1206,11 @@
  /**
   * Attempts to cancel all operations in progress on this connection.
   *
   * @param  cancelRequest  An object providing additional information about how
   *                        the cancel should be processed.
   * @param cancelRequest
   *          An object providing additional information about how the
   *          cancel should be processed.
   */
  @Override
  public void cancelAllOperations(CancelRequest cancelRequest)
  {
    // Make sure that no one can add any new operations.
@@ -1362,15 +1239,14 @@
          }
        }
        if (! (operationsInProgress.isEmpty() &&
               getPersistentSearches().isEmpty()))
        if (!(operationsInProgress.isEmpty() && getPersistentSearches()
            .isEmpty()))
        {
          lastCompletionTime.set(TimeThread.getTime());
        }
        operationsInProgress.clear();
        for (PersistentSearch persistentSearch : getPersistentSearches())
        {
          persistentSearch.cancel();
@@ -1389,16 +1265,19 @@
  /**
   * Attempts to cancel all operations in progress on this connection except the
   * operation with the specified message ID.
   * Attempts to cancel all operations in progress on this connection
   * except the operation with the specified message ID.
   *
   * @param  cancelRequest  An object providing additional information about how
   *                        the cancel should be processed.
   * @param  messageID      The message ID of the operation that should not be
   *                        canceled.
   * @param cancelRequest
   *          An object providing additional information about how the
   *          cancel should be processed.
   * @param messageID
   *          The message ID of the operation that should not be
   *          canceled.
   */
  @Override
  public void cancelAllOperationsExcept(CancelRequest cancelRequest,
                                        int messageID)
      int messageID)
  {
    // Make sure that no one can add any new operations.
    synchronized (opsInProgressLock)
@@ -1438,7 +1317,6 @@
          lastCompletionTime.set(TimeThread.getTime());
        }
        for (PersistentSearch persistentSearch : getPersistentSearches())
        {
          if (persistentSearch.getMessageID() == messageID)
@@ -1474,7 +1352,7 @@
      try
      {
        selector = Selector.open();
        if (! writeSelector.compareAndSet(null, selector))
        if (!writeSelector.compareAndSet(null, selector))
        {
          selector.close();
          selector = writeSelector.get();
@@ -1506,308 +1384,109 @@
  /**
   * Returns the total number of operations initiated on this connection.
   * Returns the total number of operations initiated on this
   * connection.
   *
   * @return the total number of operations on this connection
   */
  public long getNumberOfOperations() {
  @Override
  public long getNumberOfOperations()
  {
    long tmpNumberOfOperations;
    synchronized (operationsPerformedLock) {
    synchronized (operationsPerformedLock)
    {
      tmpNumberOfOperations = operationsPerformed;
    }
    return tmpNumberOfOperations;
  }
  /**
   * Process the information contained in the provided byte buffer as an ASN.1
   * element.  It may take several calls to this method in order to get all the
   * information necessary to decode a single ASN.1 element, but it may also be
   * possible that there are multiple elements (or at least fragments of
   * multiple elements) in a single buffer.  This will fully process whatever
   * the client provided and set up the appropriate state information to make it
   * possible to pick up in the right place the next time around.
   * Process data read.
   *
   * @param  buffer  The buffer containing the data to be processed.  It must be
   *                 ready for reading (i.e., it should have been flipped by the
   *                 caller), and the data provided must be unencrypted (e.g.,
   *                 if the client is communicating over SSL, then the
   *                 decryption should happen before calling this method).
   *
   * @return  <CODE>true</CODE> if all the data in the provided buffer was
   *          processed and the client connection can remain established, or
   *          <CODE>false</CODE> if a decoding error occurred and requests from
   *          this client should no longer be processed.  Note that if this
   *          method does return <CODE>false</CODE>, then it must have already
   *          disconnected the client, and upon returning the request handler
   *          should remove it from the associated selector.
   * @return {@code true} if this connection is still valid.
   */
  public boolean processDataRead(ByteBuffer buffer)
  public boolean processDataRead()
  {
    if (debugEnabled())
    if (this.saslPendingProvider != null)
    {
      TRACER.debugData(DebugLogLevel.VERBOSE, buffer);
      enableSASL();
    }
    int bytesAvailable = buffer.limit() - buffer.position();
    if (keepStats)
    while (true)
    {
      statTracker.updateBytesRead(bytesAvailable);
    }
    while (bytesAvailable > 0)
    {
      switch (elementReadState)
      try
      {
        case ELEMENT_READ_STATE_NEED_TYPE:
          // Read just the type and then loop again to see if there is more.
          elementType = buffer.get();
          bytesAvailable--;
          elementReadState = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
          continue;
        case ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE:
          // Get the first length byte and see if it is a single-byte or
          // multi-byte length.
          byte firstLengthByte = buffer.get();
          bytesAvailable--;
          elementLengthBytesNeeded = (firstLengthByte & 0x7F);
          if (elementLengthBytesNeeded == firstLengthByte)
          {
            elementLength = firstLengthByte;
            // If the length is zero, then it cannot be a valid LDAP message.
            if (elementLength == 0)
            {
              disconnect(DisconnectReason.PROTOCOL_ERROR, true,
                         ERR_LDAP_CLIENT_DECODE_ZERO_BYTE_VALUE.get());
              return false;
            }
            // Make sure that the element is not larger than the maximum allowed
            // message size.
            if ((connectionHandler.getMaxRequestSize() > 0) &&
                (elementLength > connectionHandler.getMaxRequestSize()))
            {
              Message m = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
                elementLength, connectionHandler.getMaxRequestSize());
              disconnect(DisconnectReason.MAX_REQUEST_SIZE_EXCEEDED, true, m);
              return false;
            }
            elementValue            = new byte[elementLength];
            elementValueBytesRead   = 0;
            elementValueBytesNeeded = elementLength;
            elementReadState        = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
            continue;
          }
          else
          {
            if (elementLengthBytesNeeded > 4)
            {
              // We cannot handle multi-byte lengths in which more than four
              // bytes are used to encode the length.
              Message m = ERR_LDAP_CLIENT_DECODE_INVALID_MULTIBYTE_LENGTH.get(
                elementLengthBytesNeeded);
              disconnect(DisconnectReason.PROTOCOL_ERROR, true, m);
              return false;
            }
            elementLength = 0x00;
            if (elementLengthBytesNeeded <= bytesAvailable)
            {
              // We can read the entire length, so do it.
              while (elementLengthBytesNeeded > 0)
              {
                elementLength = (elementLength << 8) | (buffer.get() & 0xFF);
                bytesAvailable--;
                elementLengthBytesNeeded--;
              }
              // If the length is zero, then it cannot be a valid LDAP message.
              if (elementLength == 0)
              {
                disconnect(DisconnectReason.PROTOCOL_ERROR, true,
                           ERR_LDAP_CLIENT_DECODE_ZERO_BYTE_VALUE.get());
                return false;
              }
              // Make sure that the element is not larger than the maximum
              // allowed message size.
              if ((connectionHandler.getMaxRequestSize() > 0) &&
                  (elementLength > connectionHandler.getMaxRequestSize()))
              {
                disconnect(DisconnectReason.MAX_REQUEST_SIZE_EXCEEDED, true,
                           ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
                                   elementLength,
                                   connectionHandler.getMaxRequestSize()));
                return false;
              }
              elementValue            = new byte[elementLength];
              elementValueBytesRead   = 0;
              elementValueBytesNeeded = elementLength;
              elementReadState        = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
              continue;
            }
            else
            {
              // We can't read the entire length, so just read what is
              // available.
              while (bytesAvailable > 0)
              {
                elementLength = (elementLength << 8) | (buffer.get() & 0xFF);
                bytesAvailable--;
                elementLengthBytesNeeded--;
              }
              return true;
            }
          }
        case ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES:
          if (bytesAvailable >= elementLengthBytesNeeded)
          {
            // We have enough data available to be able to read the entire
            // length.  Do so.
            while (elementLengthBytesNeeded > 0)
            {
              elementLength = (elementLength << 8) | (buffer.get() & 0xFF);
              bytesAvailable--;
              elementLengthBytesNeeded--;
            }
            // If the length is zero, then it cannot be a valid LDAP message.
            if (elementLength == 0)
            {
              disconnect(DisconnectReason.PROTOCOL_ERROR, true,
                         ERR_LDAP_CLIENT_DECODE_ZERO_BYTE_VALUE.get());
              return false;
            }
            // Make sure that the element is not larger than the maximum allowed
            // message size.
            if ((connectionHandler.getMaxRequestSize() > 0) &&
                (elementLength > connectionHandler.getMaxRequestSize()))
            {
              disconnect(DisconnectReason.MAX_REQUEST_SIZE_EXCEEDED, true,
                         ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
                                 elementLength,
                                 connectionHandler.getMaxRequestSize()));
              return false;
            }
            elementValue            = new byte[elementLength];
            elementValueBytesRead   = 0;
            elementValueBytesNeeded = elementLength;
            elementReadState        = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
            continue;
          }
          else
          {
            // We still don't have enough data to complete the length, so just
            // read as much as possible.
            while (bytesAvailable > 0)
            {
              elementLength = (elementLength << 8) | (buffer.get() & 0xFF);
              bytesAvailable--;
              elementLengthBytesNeeded--;
            }
            return true;
          }
        case ELEMENT_READ_STATE_NEED_VALUE_BYTES:
          if (bytesAvailable >= elementValueBytesNeeded)
          {
            // We have enough data available to fully read the value.  Finish
            // reading the information and convert it to an ASN.1 element.  Then
            // decode that as an LDAP message.
            buffer.get(elementValue, elementValueBytesRead,
                       elementValueBytesNeeded);
            elementValueBytesRead += elementValueBytesNeeded;
            bytesAvailable -= elementValueBytesNeeded;
            elementReadState = ELEMENT_READ_STATE_NEED_TYPE;
            ASN1Sequence requestSequence;
            try
            {
              requestSequence = ASN1Sequence.decodeAsSequence(elementType,
                                                              elementValue);
              TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
                                          requestSequence);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message m = ERR_LDAP_CLIENT_DECODE_ASN1_FAILED.get(
                String.valueOf(e));
              disconnect(DisconnectReason.PROTOCOL_ERROR, true, m);
              return false;
            }
            LDAPMessage requestMessage;
            try
            {
              requestMessage = LDAPMessage.decode(requestSequence);
              TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
                                          requestMessage);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message m = ERR_LDAP_CLIENT_DECODE_LDAP_MESSAGE_FAILED.get(
                String.valueOf(e));
              disconnect(DisconnectReason.PROTOCOL_ERROR, true, m);
              return false;
            }
            if (processLDAPMessage(requestMessage))
            {
              continue;
            }
            else
            {
              return false;
            }
          }
          else
          {
            // We can't read all the value, so just read as much as we have
            // available and pick it up again the next time around.
            buffer.get(elementValue, elementValueBytesRead, bytesAvailable);
            elementValueBytesRead   += bytesAvailable;
            elementValueBytesNeeded -= bytesAvailable;
            return true;
          }
        default:
          // This should never happen.  There is an invalid internal read state.
          // The only recourse that we have is to log a message and disconnect
          // the client.
          Message message =
              ERR_LDAP_CLIENT_INVALID_DECODE_STATE.get(elementReadState);
          logError(message);
          disconnect(DisconnectReason.SERVER_ERROR, true, message);
        int result = asn1Reader.processChannelData();
        if (result < 0)
        {
          // The connection has been closed by the client. Disconnect
          // and return.
          disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null);
          return false;
        }
        if (result == 0)
        {
          // We have read all the data that there is to read right now
          // (or there wasn't any in the first place). Just return and
          // wait for future notification.
          return true;
        }
        else
        {
          // Decode all complete elements from the last read
          while (asn1Reader.elementAvailable())
          {
            processLDAPMessage(LDAPReader.readMessage(asn1Reader));
          }
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        Message m =
            ERR_LDAP_CLIENT_DECODE_LDAP_MESSAGE_FAILED.get(String
                .valueOf(e));
        disconnect(DisconnectReason.PROTOCOL_ERROR, true, m);
        return false;
      }
    }
  }
    // If we've gotten here, then all of the data must have been processed
    // properly so we can return true.
  /**
   * Process the information contained in the provided byte buffer as an
   * ASN.1 element. It may take several calls to this method in order to
   * get all the information necessary to decode a single ASN.1 element,
   * but it may also be possible that there are multiple elements (or at
   * least fragments of multiple elements) in a single buffer. This will
   * fully process whatever the client provided and set up the
   * appropriate state information to make it possible to pick up in the
   * right place the next time around.
   *
   * @param buffer
   *          The buffer containing the data to be processed. It must be
   *          ready for reading (i.e., it should have been flipped by
   *          the caller), and the data provided must be unencrypted
   *          (e.g., if the client is communicating over SSL, then the
   *          decryption should happen before calling this method).
   * @return <CODE>true</CODE> if all the data in the provided buffer
   *         was processed and the client connection can remain
   *         established, or <CODE>false</CODE> if a decoding error
   *         occurred and requests from this client should no longer be
   *         processed. Note that if this method does return
   *         <CODE>false</CODE>, then it must have already disconnected
   *         the client, and upon returning the request handler should
   *         remove it from the associated selector.
   */
  @Override
  public boolean processDataRead(ByteBuffer buffer)
  {
    // this is no longer used.
    return true;
  }
@@ -1815,16 +1494,16 @@
  /**
   * Processes the provided LDAP message read from the client and takes
   * whatever action is appropriate.  For most requests, this will include
   * placing the operation in the work queue.  Certain requests (in particular,
   * abandons and unbinds) will be processed directly.
   * whatever action is appropriate. For most requests, this will
   * include placing the operation in the work queue. Certain requests
   * (in particular, abandons and unbinds) will be processed directly.
   *
   * @param  message  The LDAP message to process.
   *
   * @return  <CODE>true</CODE> if the appropriate action was taken for the
   *          request, or <CODE>false</CODE> if there was a fatal error and
   *          the client has been disconnected as a result, or if the client
   *          unbound from the server.
   * @param message
   *          The LDAP message to process.
   * @return <CODE>true</CODE> if the appropriate action was taken for
   *         the request, or <CODE>false</CODE> if there was a fatal
   *         error and the client has been disconnected as a result, or
   *         if the client unbound from the server.
   */
  private boolean processLDAPMessage(LDAPMessage message)
  {
@@ -1833,126 +1512,124 @@
      statTracker.updateMessageRead(message);
      this.getNetworkGroup().updateMessageRead(message);
    }
    synchronized (operationsPerformedLock) {
    synchronized (operationsPerformedLock)
    {
      operationsPerformed++;
    }
    ArrayList<Control> opControls;
    ArrayList<LDAPControl> ldapControls = message.getControls();
    if ((ldapControls == null) || ldapControls.isEmpty())
    {
      opControls = null;
    }
    else
    {
      opControls = new ArrayList<Control>(ldapControls.size());
      for (LDAPControl c : ldapControls)
      {
        opControls.add(c.getControl());
      }
    }
    List<Control> opControls = message.getControls();
    // FIXME -- See if there is a bind in progress. If so, then deny
    // most kinds of operations.
    // FIXME -- See if there is a bind in progress.  If so, then deny most
    // kinds of operations.
    // Figure out what type of operation we're dealing with based on the LDAP
    // message.  Abandon and unbind requests will be processed here.  All other
    // types of requests will be encapsulated into operations and put into the
    // work queue to be picked up by a worker thread.  Any other kinds of
    // LDAP messages (e.g., response messages) are illegal and will result in
    // the connection being terminated.
    // Figure out what type of operation we're dealing with based on the
    // LDAP message. Abandon and unbind requests will be processed here.
    // All other types of requests will be encapsulated into operations
    // and append into the work queue to be picked up by a worker
    // thread. Any other kinds of LDAP messages (e.g., response
    // messages) are illegal and will result in the connection being
    // terminated.
    try
    {
      boolean result;
      switch (message.getProtocolOpType())
      {
        case OP_TYPE_ABANDON_REQUEST:
          if (keepStats) this.abandonMonitor.start();
          result=processAbandonRequest(message, opControls);
          if (keepStats) {
              this.abandonMonitor.stop();
              this.abandonMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        case OP_TYPE_ADD_REQUEST:
          if (keepStats) this.addMonitor.start();
          result=processAddRequest(message, opControls);
          if (keepStats) {
              this.addMonitor.stop();
              this.addMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        case OP_TYPE_BIND_REQUEST:
          if (keepStats) this.bindMonitor.start();
          result=processBindRequest(message, opControls);
          if (keepStats) {
              this.bindMonitor.stop();
              this.bindMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        case OP_TYPE_COMPARE_REQUEST:
          if (keepStats) this.compareMonitor.start();
          result=processCompareRequest(message, opControls);
          if (keepStats) {
              this.compareMonitor.stop();
              this.compareMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        case OP_TYPE_DELETE_REQUEST:
          if (keepStats) this.delMonitor.start();
          result=processDeleteRequest(message, opControls);
          if (keepStats) {
              this.delMonitor.stop();
              this.delMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        case OP_TYPE_EXTENDED_REQUEST:
          if (keepStats) this.extendedMonitor.start();
          result=processExtendedRequest(message, opControls);
          if (keepStats) {
              this.extendedMonitor.stop();
              this.extendedMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        case OP_TYPE_MODIFY_REQUEST:
          if (keepStats) this.modMonitor.start();
          result=processModifyRequest(message, opControls);
          if (keepStats) {
              this.modMonitor.stop();
              this.modMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        case OP_TYPE_MODIFY_DN_REQUEST:
          if (keepStats) this.moddnMonitor.start();
          result=processModifyDNRequest(message, opControls);
          if (keepStats) {
              this.moddnMonitor.stop();
              this.moddnMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        case OP_TYPE_SEARCH_REQUEST:
          if (keepStats) this.searchMonitor.start();
          result=processSearchRequest(message, opControls);
          if (keepStats) {
              this.searchMonitor.stop();
              this.searchMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        case OP_TYPE_UNBIND_REQUEST:
          if (keepStats) this.unbindMonitor.start();
          result=processUnbindRequest(message, opControls);
          if (keepStats) {
              this.unbindMonitor.stop();
              this.unbindMonitor.updateMonitorProvider(statTracker);
          }
          return result;
        default:
          Message msg = ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(
                  message.getProtocolOpName(), message.getMessageID());
          disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
          return false;
      case OP_TYPE_ABANDON_REQUEST:
        if (keepStats) this.abandonMonitor.start();
        result = processAbandonRequest(message, opControls);
        if (keepStats)
        {
          this.abandonMonitor.stop();
          this.abandonMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      case OP_TYPE_ADD_REQUEST:
        if (keepStats) this.addMonitor.start();
        result = processAddRequest(message, opControls);
        if (keepStats)
        {
          this.addMonitor.stop();
          this.addMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      case OP_TYPE_BIND_REQUEST:
        if (keepStats) this.bindMonitor.start();
        result = processBindRequest(message, opControls);
        if (keepStats)
        {
          this.bindMonitor.stop();
          this.bindMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      case OP_TYPE_COMPARE_REQUEST:
        if (keepStats) this.compareMonitor.start();
        result = processCompareRequest(message, opControls);
        if (keepStats)
        {
          this.compareMonitor.stop();
          this.compareMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      case OP_TYPE_DELETE_REQUEST:
        if (keepStats) this.delMonitor.start();
        result = processDeleteRequest(message, opControls);
        if (keepStats)
        {
          this.delMonitor.stop();
          this.delMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      case OP_TYPE_EXTENDED_REQUEST:
        if (keepStats) this.extendedMonitor.start();
        result = processExtendedRequest(message, opControls);
        if (keepStats)
        {
          this.extendedMonitor.stop();
          this.extendedMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      case OP_TYPE_MODIFY_REQUEST:
        if (keepStats) this.modMonitor.start();
        result = processModifyRequest(message, opControls);
        if (keepStats)
        {
          this.modMonitor.stop();
          this.modMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      case OP_TYPE_MODIFY_DN_REQUEST:
        if (keepStats) this.moddnMonitor.start();
        result = processModifyDNRequest(message, opControls);
        if (keepStats)
        {
          this.moddnMonitor.stop();
          this.moddnMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      case OP_TYPE_SEARCH_REQUEST:
        if (keepStats) this.searchMonitor.start();
        result = processSearchRequest(message, opControls);
        if (keepStats)
        {
          this.searchMonitor.stop();
          this.searchMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      case OP_TYPE_UNBIND_REQUEST:
        if (keepStats) this.unbindMonitor.start();
        result = processUnbindRequest(message, opControls);
        if (keepStats)
        {
          this.unbindMonitor.stop();
          this.unbindMonitor.updateMonitorProvider(statTracker);
        }
        return result;
      default:
        Message msg =
            ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(message
                .getProtocolOpName(), message.getMessageID());
        disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
        return false;
      }
    }
    catch (Exception e)
@@ -1962,9 +1639,10 @@
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message msg = ERR_LDAP_DISCONNECT_DUE_TO_PROCESSING_FAILURE.get(
              message.getProtocolOpName(),
              message.getMessageID(), String.valueOf(e));
      Message msg =
          ERR_LDAP_DISCONNECT_DUE_TO_PROCESSING_FAILURE.get(message
              .getProtocolOpName(), message.getMessageID(), String
              .valueOf(e));
      disconnect(DisconnectReason.SERVER_ERROR, true, msg);
      return false;
    }
@@ -1975,24 +1653,26 @@
  /**
   * Processes the provided LDAP message as an abandon request.
   *
   * @param  message   The LDAP message containing the abandon request to
   *                   process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the abandon request to
   *          process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processAbandonRequest(LDAPMessage message,
                                        ArrayList<Control> controls)
      List<Control> controls)
  {
    AbandonRequestProtocolOp protocolOp = message.getAbandonRequestProtocolOp();
    AbandonRequestProtocolOp protocolOp =
        message.getAbandonRequestProtocolOp();
    AbandonOperationBasis abandonOp =
         new AbandonOperationBasis(this, nextOperationID.getAndIncrement(),
                              message.getMessageID(), controls,
                              protocolOp.getIDToAbandon());
        new AbandonOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getIDToAbandon());
    abandonOp.run();
    if (keepStats && (abandonOp.getResultCode() == ResultCode.CANCELED))
@@ -2008,37 +1688,39 @@
  /**
   * Processes the provided LDAP message as an add request.
   *
   * @param  message   The LDAP message containing the add request to process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the add request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processAddRequest(LDAPMessage message,
                                    ArrayList<Control> controls)
      List<Control> controls)
  {
    if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
    if ((ldapVersion == 2) && (controls != null)
        && (!controls.isEmpty()))
    {
      // LDAPv2 clients aren't allowed to send controls.
      AddResponseProtocolOp responseOp =
           new AddResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
                    ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp));
          new AddResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
                 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      return false;
    }
    // Create the add operation and add it into the work queue.
    AddRequestProtocolOp protocolOp = message.getAddRequestProtocolOp();
    AddOperationBasis addOp =
         new AddOperationBasis(this, nextOperationID.getAndIncrement(),
                          message.getMessageID(), controls, protocolOp.getDN(),
                          protocolOp.getAttributes());
        new AddOperationBasis(this, nextOperationID.getAndIncrement(),
            message.getMessageID(), controls, protocolOp.getDN(),
            protocolOp.getAttributes());
    try
    {
@@ -2052,24 +1734,14 @@
      }
      AddResponseProtocolOp responseOp =
           new AddResponseProtocolOp(de.getResultCode().getIntValue(),
                                     de.getMessageObject(), de.getMatchedDN(),
                                     de.getReferralURLs());
          new AddResponseProtocolOp(de.getResultCode().getIntValue(),
              de.getMessageObject(), de.getMatchedDN(), de
                  .getReferralURLs());
      List<Control> responseControls = addOp.getResponseControls();
      ArrayList<LDAPControl> responseLDAPControls =
           new ArrayList<LDAPControl>(responseControls.size());
      for (Control c : responseControls)
      {
        responseLDAPControls.add(new LDAPControl(c));
      }
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp,
                                      responseLDAPControls));
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp, addOp.getResponseControls()));
    }
    return connectionValid;
  }
@@ -2078,91 +1750,92 @@
  /**
   * Processes the provided LDAP message as a bind request.
   *
   * @param  message   The LDAP message containing the bind request to process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the bind request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processBindRequest(LDAPMessage message,
                                     ArrayList<Control> controls)
      List<Control> controls)
  {
    BindRequestProtocolOp protocolOp = message.getBindRequestProtocolOp();
    BindRequestProtocolOp protocolOp =
        message.getBindRequestProtocolOp();
    // See if this is an LDAPv2 bind request, and if so whether that should be
    // allowed.
    // See if this is an LDAPv2 bind request, and if so whether that
    // should be allowed.
    String versionString;
    switch (ldapVersion = protocolOp.getProtocolVersion())
    {
      case 2:
        versionString = "2";
    case 2:
      versionString = "2";
        if (! connectionHandler.allowLDAPv2())
        {
          BindResponseProtocolOp responseOp =
               new BindResponseProtocolOp(
                        LDAPResultCode.INAPPROPRIATE_AUTHENTICATION,
                        ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
          sendLDAPMessage(securityProvider,
                          new LDAPMessage(message.getMessageID(), responseOp));
          disconnect(DisconnectReason.PROTOCOL_ERROR, false,
                     ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
          return false;
        }
      if (!connectionHandler.allowLDAPv2())
      {
        BindResponseProtocolOp responseOp =
            new BindResponseProtocolOp(
                LDAPResultCode.INAPPROPRIATE_AUTHENTICATION,
                ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
        sendLDAPMessage(new LDAPMessage(message.getMessageID(),
            responseOp));
        disconnect(DisconnectReason.PROTOCOL_ERROR, false,
            ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
        return false;
      }
        if ((controls != null) && (! controls.isEmpty()))
        {
          // LDAPv2 clients aren't allowed to send controls.
          BindResponseProtocolOp responseOp =
               new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
                        ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
          sendLDAPMessage(securityProvider,
                          new LDAPMessage(message.getMessageID(), responseOp));
          disconnect(DisconnectReason.PROTOCOL_ERROR, false,
                     ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
          return false;
        }
      if ((controls != null) && (!controls.isEmpty()))
      {
        // LDAPv2 clients aren't allowed to send controls.
        BindResponseProtocolOp responseOp =
            new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
                ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
        sendLDAPMessage(new LDAPMessage(message.getMessageID(),
            responseOp));
        disconnect(DisconnectReason.PROTOCOL_ERROR, false,
            ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
        return false;
      }
        break;
      case 3:
        versionString = "3";
        break;
      default:
        versionString = String.valueOf(ldapVersion);
        break;
      break;
    case 3:
      versionString = "3";
      break;
    default:
      versionString = String.valueOf(ldapVersion);
      break;
    }
    ASN1OctetString bindDN = protocolOp.getDN();
    ByteString bindDN = protocolOp.getDN();
    BindOperationBasis bindOp;
    switch (protocolOp.getAuthenticationType())
    {
      case SIMPLE:
        bindOp = new BindOperationBasis(this, nextOperationID.getAndIncrement(),
                                   message.getMessageID(), controls,
                                   versionString, bindDN,
                                   protocolOp.getSimplePassword());
        break;
      case SASL:
        bindOp = new BindOperationBasis(this, nextOperationID.getAndIncrement(),
                                   message.getMessageID(), controls,
                                   versionString, bindDN,
                                   protocolOp.getSASLMechanism(),
                                   protocolOp.getSASLCredentials());
        break;
      default:
        // This is an invalid authentication type, and therefore a protocol
        // error.  As per RFC 2251, a protocol error in a bind request must
        // result in terminating the connection.
        Message msg =
                ERR_LDAP_INVALID_BIND_AUTH_TYPE.get(message.getMessageID(),
                          String.valueOf(protocolOp.getAuthenticationType()));
        disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
        return false;
    case SIMPLE:
      bindOp =
          new BindOperationBasis(this, nextOperationID
              .getAndIncrement(), message.getMessageID(), controls,
              versionString, bindDN, protocolOp.getSimplePassword());
      break;
    case SASL:
      bindOp =
          new BindOperationBasis(this, nextOperationID
              .getAndIncrement(), message.getMessageID(), controls,
              versionString, bindDN, protocolOp.getSASLMechanism(),
              protocolOp.getSASLCredentials());
      break;
    default:
      // This is an invalid authentication type, and therefore a
      // protocol error. As per RFC 2251, a protocol error in a bind
      // request must result in terminating the connection.
      Message msg =
          ERR_LDAP_INVALID_BIND_AUTH_TYPE.get(message.getMessageID(),
              String.valueOf(protocolOp.getAuthenticationType()));
      disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
      return false;
    }
    // Add the operation into the work queue.
@@ -2178,32 +1851,23 @@
      }
      BindResponseProtocolOp responseOp =
           new BindResponseProtocolOp(de.getResultCode().getIntValue(),
                                      de.getMessageObject(), de.getMatchedDN(),
                                      de.getReferralURLs());
          new BindResponseProtocolOp(de.getResultCode().getIntValue(),
              de.getMessageObject(), de.getMatchedDN(), de
                  .getReferralURLs());
      List<Control> responseControls = bindOp.getResponseControls();
      ArrayList<LDAPControl> responseLDAPControls =
           new ArrayList<LDAPControl>(responseControls.size());
      for (Control c : responseControls)
      {
        responseLDAPControls.add(new LDAPControl(c));
      }
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp,
                                      responseLDAPControls));
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp, bindOp.getResponseControls()));
      // If it was a protocol error, then terminate the connection.
      if (de.getResultCode() == ResultCode.PROTOCOL_ERROR)
      {
        Message msg = ERR_LDAP_DISCONNECT_DUE_TO_BIND_PROTOCOL_ERROR.get(
                message.getMessageID(), de.getMessageObject());
        Message msg =
            ERR_LDAP_DISCONNECT_DUE_TO_BIND_PROTOCOL_ERROR.get(message
                .getMessageID(), de.getMessageObject());
        disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
      }
    }
    return connectionValid;
  }
@@ -2212,38 +1876,41 @@
  /**
   * Processes the provided LDAP message as a compare request.
   *
   * @param  message   The LDAP message containing the compare request to
   *                   process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the compare request to
   *          process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processCompareRequest(LDAPMessage message,
                                        ArrayList<Control> controls)
      List<Control> controls)
  {
    if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
    if ((ldapVersion == 2) && (controls != null)
        && (!controls.isEmpty()))
    {
      // LDAPv2 clients aren't allowed to send controls.
      CompareResponseProtocolOp responseOp =
           new CompareResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
                    ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp));
          new CompareResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
                 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      return false;
    }
    CompareRequestProtocolOp protocolOp = message.getCompareRequestProtocolOp();
    CompareRequestProtocolOp protocolOp =
        message.getCompareRequestProtocolOp();
    CompareOperationBasis compareOp =
         new CompareOperationBasis(this, nextOperationID.getAndIncrement(),
                              message.getMessageID(), controls,
                              protocolOp.getDN(), protocolOp.getAttributeType(),
                              protocolOp.getAssertionValue());
        new CompareOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getDN(), protocolOp.getAttributeType(),
            protocolOp.getAssertionValue());
    // Add the operation into the work queue.
    try
@@ -2258,25 +1925,14 @@
      }
      CompareResponseProtocolOp responseOp =
           new CompareResponseProtocolOp(de.getResultCode().getIntValue(),
                                         de.getMessageObject(),
                                         de.getMatchedDN(),
                                         de.getReferralURLs());
          new CompareResponseProtocolOp(de.getResultCode()
              .getIntValue(), de.getMessageObject(), de.getMatchedDN(),
              de.getReferralURLs());
      List<Control> responseControls = compareOp.getResponseControls();
      ArrayList<LDAPControl> responseLDAPControls =
           new ArrayList<LDAPControl>(responseControls.size());
      for (Control c : responseControls)
      {
        responseLDAPControls.add(new LDAPControl(c));
      }
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp,
                                      responseLDAPControls));
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp, compareOp.getResponseControls()));
    }
    return connectionValid;
  }
@@ -2285,37 +1941,39 @@
  /**
   * Processes the provided LDAP message as a delete request.
   *
   * @param  message   The LDAP message containing the delete request to
   *                   process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the delete request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processDeleteRequest(LDAPMessage message,
                                       ArrayList<Control> controls)
      List<Control> controls)
  {
    if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
    if ((ldapVersion == 2) && (controls != null)
        && (!controls.isEmpty()))
    {
      // LDAPv2 clients aren't allowed to send controls.
      DeleteResponseProtocolOp responseOp =
           new DeleteResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
                    ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp));
          new DeleteResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
                 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      return false;
    }
    DeleteRequestProtocolOp protocolOp = message.getDeleteRequestProtocolOp();
    DeleteRequestProtocolOp protocolOp =
        message.getDeleteRequestProtocolOp();
    DeleteOperationBasis deleteOp =
         new DeleteOperationBasis(this, nextOperationID.getAndIncrement(),
                             message.getMessageID(), controls,
                             protocolOp.getDN());
        new DeleteOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getDN());
    // Add the operation into the work queue.
    try
@@ -2330,25 +1988,14 @@
      }
      DeleteResponseProtocolOp responseOp =
           new DeleteResponseProtocolOp(de.getResultCode().getIntValue(),
                                        de.getMessageObject(),
                                        de.getMatchedDN(),
                                        de.getReferralURLs());
          new DeleteResponseProtocolOp(
              de.getResultCode().getIntValue(), de.getMessageObject(),
              de.getMatchedDN(), de.getReferralURLs());
      List<Control> responseControls = deleteOp.getResponseControls();
      ArrayList<LDAPControl> responseLDAPControls =
           new ArrayList<LDAPControl>(responseControls.size());
      for (Control c : responseControls)
      {
        responseLDAPControls.add(new LDAPControl(c));
      }
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp,
                                      responseLDAPControls));
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp, deleteOp.getResponseControls()));
    }
    return connectionValid;
  }
@@ -2357,43 +2004,44 @@
  /**
   * Processes the provided LDAP message as an extended request.
   *
   * @param  message   The LDAP message containing the extended request to
   *                   process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the extended request to
   *          process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processExtendedRequest(LDAPMessage message,
                                         ArrayList<Control> controls)
      List<Control> controls)
  {
    // See if this is an LDAPv2 client.  If it is, then they should not be
    // issuing extended requests.  We can't send a response that we can be sure
    // they can understand, so we have no choice but to close the connection.
    // See if this is an LDAPv2 client. If it is, then they should not
    // be issuing extended requests. We can't send a response that we
    // can be sure they can understand, so we have no choice but to
    // close the connection.
    if (ldapVersion == 2)
    {
      Message msg = ERR_LDAPV2_EXTENDED_REQUEST_NOT_ALLOWED.get(
          getConnectionID(), message.getMessageID());
      Message msg =
          ERR_LDAPV2_EXTENDED_REQUEST_NOT_ALLOWED.get(
              getConnectionID(), message.getMessageID());
      logError(msg);
      disconnect(DisconnectReason.PROTOCOL_ERROR, false, msg);
      return false;
    }
    // FIXME -- Do we need to handle certain types of request here?
    // -- StartTLS requests
    // -- Cancel requests
    ExtendedRequestProtocolOp protocolOp =
         message.getExtendedRequestProtocolOp();
        message.getExtendedRequestProtocolOp();
    ExtendedOperationBasis extendedOp =
         new ExtendedOperationBasis(this, nextOperationID.getAndIncrement(),
                               message.getMessageID(), controls,
                               protocolOp.getOID(), protocolOp.getValue());
        new ExtendedOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getOID(), protocolOp.getValue());
    // Add the operation into the work queue.
    try
@@ -2408,25 +2056,14 @@
      }
      ExtendedResponseProtocolOp responseOp =
           new ExtendedResponseProtocolOp(de.getResultCode().getIntValue(),
                                          de.getMessageObject(),
                                          de.getMatchedDN(),
                                          de.getReferralURLs());
          new ExtendedResponseProtocolOp(de.getResultCode()
              .getIntValue(), de.getMessageObject(), de.getMatchedDN(),
              de.getReferralURLs());
      List<Control> responseControls = extendedOp.getResponseControls();
      ArrayList<LDAPControl> responseLDAPControls =
           new ArrayList<LDAPControl>(responseControls.size());
      for (Control c : responseControls)
      {
        responseLDAPControls.add(new LDAPControl(c));
      }
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp,
                                      responseLDAPControls));
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp, extendedOp.getResponseControls()));
    }
    return connectionValid;
  }
@@ -2435,37 +2072,39 @@
  /**
   * Processes the provided LDAP message as a modify request.
   *
   * @param  message   The LDAP message containing the modify request to
   *                   process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the modify request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processModifyRequest(LDAPMessage message,
                                       ArrayList<Control> controls)
      List<Control> controls)
  {
    if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
    if ((ldapVersion == 2) && (controls != null)
        && (!controls.isEmpty()))
    {
      // LDAPv2 clients aren't allowed to send controls.
      ModifyResponseProtocolOp responseOp =
           new ModifyResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
                    ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp));
          new ModifyResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
                 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      return false;
    }
    ModifyRequestProtocolOp protocolOp = message.getModifyRequestProtocolOp();
    ModifyRequestProtocolOp protocolOp =
        message.getModifyRequestProtocolOp();
    ModifyOperationBasis modifyOp =
         new ModifyOperationBasis(this, nextOperationID.getAndIncrement(),
                             message.getMessageID(), controls,
                             protocolOp.getDN(), protocolOp.getModifications());
        new ModifyOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getDN(), protocolOp.getModifications());
    // Add the operation into the work queue.
    try
@@ -2480,25 +2119,14 @@
      }
      ModifyResponseProtocolOp responseOp =
           new ModifyResponseProtocolOp(de.getResultCode().getIntValue(),
                                        de.getMessageObject(),
                                        de.getMatchedDN(),
                                        de.getReferralURLs());
          new ModifyResponseProtocolOp(
              de.getResultCode().getIntValue(), de.getMessageObject(),
              de.getMatchedDN(), de.getReferralURLs());
      List<Control> responseControls = modifyOp.getResponseControls();
      ArrayList<LDAPControl> responseLDAPControls =
           new ArrayList<LDAPControl>(responseControls.size());
      for (Control c : responseControls)
      {
        responseLDAPControls.add(new LDAPControl(c));
      }
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp,
                                      responseLDAPControls));
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp, modifyOp.getResponseControls()));
    }
    return connectionValid;
  }
@@ -2507,40 +2135,41 @@
  /**
   * Processes the provided LDAP message as a modify DN request.
   *
   * @param  message   The LDAP message containing the modify DN request to
   *                   process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the modify DN request to
   *          process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processModifyDNRequest(LDAPMessage message,
                                         ArrayList<Control> controls)
      List<Control> controls)
  {
    if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
    if ((ldapVersion == 2) && (controls != null)
        && (!controls.isEmpty()))
    {
      // LDAPv2 clients aren't allowed to send controls.
      ModifyDNResponseProtocolOp responseOp =
           new ModifyDNResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
                    ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp));
          new ModifyDNResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
                 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      return false;
    }
    ModifyDNRequestProtocolOp protocolOp =
         message.getModifyDNRequestProtocolOp();
        message.getModifyDNRequestProtocolOp();
    ModifyDNOperationBasis modifyDNOp =
         new ModifyDNOperationBasis(this, nextOperationID.getAndIncrement(),
                               message.getMessageID(), controls,
                               protocolOp.getEntryDN(), protocolOp.getNewRDN(),
                               protocolOp.deleteOldRDN(),
                               protocolOp.getNewSuperior());
        new ModifyDNOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getEntryDN(), protocolOp.getNewRDN(), protocolOp
                .deleteOldRDN(), protocolOp.getNewSuperior());
    // Add the operation into the work queue.
    try
@@ -2555,25 +2184,14 @@
      }
      ModifyDNResponseProtocolOp responseOp =
           new ModifyDNResponseProtocolOp(de.getResultCode().getIntValue(),
                                          de.getMessageObject(),
                                          de.getMatchedDN(),
                                          de.getReferralURLs());
          new ModifyDNResponseProtocolOp(de.getResultCode()
              .getIntValue(), de.getMessageObject(), de.getMatchedDN(),
              de.getReferralURLs());
      List<Control> responseControls = modifyDNOp.getResponseControls();
      ArrayList<LDAPControl> responseLDAPControls =
           new ArrayList<LDAPControl>(responseControls.size());
      for (Control c : responseControls)
      {
        responseLDAPControls.add(new LDAPControl(c));
      }
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp,
                                      responseLDAPControls));
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp, modifyDNOp.getResponseControls()));
    }
    return connectionValid;
  }
@@ -2582,42 +2200,42 @@
  /**
   * Processes the provided LDAP message as a search request.
   *
   * @param  message   The LDAP message containing the search request to
   *                   process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the search request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processSearchRequest(LDAPMessage message,
                                       ArrayList<Control> controls)
      List<Control> controls)
  {
    if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
    if ((ldapVersion == 2) && (controls != null)
        && (!controls.isEmpty()))
    {
      // LDAPv2 clients aren't allowed to send controls.
      SearchResultDoneProtocolOp responseOp =
           new SearchResultDoneProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
                    ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp));
          new SearchResultDoneProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
                 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      return false;
    }
    SearchRequestProtocolOp protocolOp = message.getSearchRequestProtocolOp();
    SearchRequestProtocolOp protocolOp =
        message.getSearchRequestProtocolOp();
    SearchOperationBasis searchOp =
         new SearchOperationBasis(this, nextOperationID.getAndIncrement(),
                             message.getMessageID(), controls,
                             protocolOp.getBaseDN(), protocolOp.getScope(),
                             protocolOp.getDereferencePolicy(),
                             protocolOp.getSizeLimit(),
                             protocolOp.getTimeLimit(),
                             protocolOp.getTypesOnly(), protocolOp.getFilter(),
                             protocolOp.getAttributes());
        new SearchOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls,
            protocolOp.getBaseDN(), protocolOp.getScope(), protocolOp
                .getDereferencePolicy(), protocolOp.getSizeLimit(),
            protocolOp.getTimeLimit(), protocolOp.getTypesOnly(),
            protocolOp.getFilter(), protocolOp.getAttributes());
    // Add the operation into the work queue.
    try
@@ -2632,25 +2250,14 @@
      }
      SearchResultDoneProtocolOp responseOp =
           new SearchResultDoneProtocolOp(de.getResultCode().getIntValue(),
                                          de.getMessageObject(),
                                          de.getMatchedDN(),
                                          de.getReferralURLs());
          new SearchResultDoneProtocolOp(de.getResultCode()
              .getIntValue(), de.getMessageObject(), de.getMatchedDN(),
              de.getReferralURLs());
      List<Control> responseControls = searchOp.getResponseControls();
      ArrayList<LDAPControl> responseLDAPControls =
           new ArrayList<LDAPControl>(responseControls.size());
      for (Control c : responseControls)
      {
        responseLDAPControls.add(new LDAPControl(c));
      }
      sendLDAPMessage(securityProvider,
                      new LDAPMessage(message.getMessageID(), responseOp,
                                      responseLDAPControls));
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp, searchOp.getResponseControls()));
    }
    return connectionValid;
  }
@@ -2659,22 +2266,22 @@
  /**
   * Processes the provided LDAP message as an unbind request.
   *
   * @param  message   The LDAP message containing the unbind request to
   *                   process.
   * @param  controls  The set of pre-decoded request controls contained in the
   *                   message.
   *
   * @return  <CODE>true</CODE> if the request was processed successfully, or
   *          <CODE>false</CODE> if not and the connection has been closed as a
   *          result (it is the responsibility of this method to close the
   *          connection).
   * @param message
   *          The LDAP message containing the unbind request to process.
   * @param controls
   *          The set of pre-decoded request controls contained in the
   *          message.
   * @return <CODE>true</CODE> if the request was processed
   *         successfully, or <CODE>false</CODE> if not and the
   *         connection has been closed as a result (it is the
   *         responsibility of this method to close the connection).
   */
  private boolean processUnbindRequest(LDAPMessage message,
                                       ArrayList<Control> controls)
      List<Control> controls)
  {
    UnbindOperationBasis unbindOp =
         new UnbindOperationBasis(this, nextOperationID.getAndIncrement(),
                              message.getMessageID(), controls);
        new UnbindOperationBasis(this, nextOperationID
            .getAndIncrement(), message.getMessageID(), controls);
    unbindOp.run();
@@ -2687,6 +2294,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public String getMonitorSummary()
  {
    StringBuilder buffer = new StringBuilder();
@@ -2713,9 +2321,9 @@
    }
    buffer.append("\" security=\"");
    if (securityProvider.isSecure())
    if (isSecure())
    {
      buffer.append(securityProvider.getSecurityMechanismName());
      buffer.append(activeProvider.getName());
    }
    else
    {
@@ -2732,11 +2340,13 @@
  /**
   * Appends a string representation of this client connection to the provided
   * buffer.
   * Appends a string representation of this client connection to the
   * provided buffer.
   *
   * @param  buffer  The buffer to which the information should be appended.
   * @param buffer
   *          The buffer to which the information should be appended.
   */
  @Override
  public void toString(StringBuilder buffer)
  {
    buffer.append("LDAP client connection from ");
@@ -2752,218 +2362,90 @@
  /**
   * Indicates whether TLS protection is actually available for the underlying
   * client connection.  If there is any reason that TLS protection cannot be
   * enabled on this client connection, then it should be appended to the
   * provided buffer.
   * Indicates whether TLS protection is actually available for the
   * underlying client connection. If there is any reason that TLS
   * protection cannot be enabled on this client connection, then it
   * should be appended to the provided buffer.
   *
   * @param  unavailableReason  The buffer used to hold the reason that TLS is
   *                            not available on the underlying client
   *                            connection.
   *
   * @return  <CODE>true</CODE> if TLS is available on the underlying client
   *          connection, or <CODE>false</CODE> if it is not.
   * @param unavailableReason
   *          The buffer used to hold the reason that TLS is not
   *          available on the underlying client connection.
   * @return <CODE>true</CODE> if TLS is available on the underlying
   *         client connection, or <CODE>false</CODE> if it is not.
   */
  public boolean tlsProtectionAvailable(MessageBuilder unavailableReason)
  public boolean isTLSAvailable(MessageBuilder unavailableReason)
  {
    // Make sure that this client connection does not already have some other
    // security provider enabled.
    if (! (securityProvider instanceof NullConnectionSecurityProvider))
    if (isSecure() && activeProvider.getName().equals("TLS"))
    {
      unavailableReason.append(ERR_LDAP_TLS_EXISTING_SECURITY_PROVIDER.get(
              securityProvider.getSecurityMechanismName()));
      // TODO SASLPhase2 more general message
      unavailableReason.append(ERR_LDAP_TLS_EXISTING_SECURITY_PROVIDER
          .get(activeProvider.getName()));
      return false;
    }
    // Make sure that the connection handler allows the use of the StartTLS
    // operation.
    if (! connectionHandler.allowStartTLS())
    // Make sure that the connection handler allows the use of the
    // StartTLS operation.
    if (!connectionHandler.allowStartTLS())
    {
      unavailableReason.append(ERR_LDAP_TLS_STARTTLS_NOT_ALLOWED.get());
      return false;
    }
    // Make sure that the TLS security provider is available.
    if (tlsSecurityProvider == null)
    try
    {
      try
      {
        TLSConnectionSecurityProvider tlsProvider =
             new TLSConnectionSecurityProvider();
        tlsProvider.initializeConnectionSecurityProvider(null);
        tlsProvider.setSSLClientAuthPolicy(
             connectionHandler.getSSLClientAuthPolicy());
        tlsProvider.setEnabledProtocols(
             connectionHandler.getEnabledSSLProtocols());
        tlsProvider.setEnabledCipherSuites(
             connectionHandler.getEnabledSSLCipherSuites());
        tlsSecurityProvider = (TLSConnectionSecurityProvider)
                              tlsProvider.newInstance(this, clientChannel);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        tlsSecurityProvider = null;
        unavailableReason.append(ERR_LDAP_TLS_CANNOT_CREATE_TLS_PROVIDER.get(
                stackTraceToSingleLineString(e)));
        return false;
      }
      TLSByteChannel tlsByteChannel =
          connectionHandler.getTLSByteChannel(this, clientChannel);
      setTLSPendingProvider(tlsByteChannel);
    }
    // If we've gotten here, then everything looks OK.
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      unavailableReason.append(ERR_LDAP_TLS_CANNOT_CREATE_TLS_PROVIDER
          .get(stackTraceToSingleLineString(de)));
      return false;
    }
    return true;
  }
  /**
   * Installs the TLS connection security provider on this client connection.
   * If an error occurs in the process, then the underlying client connection
   * must be terminated and an exception must be thrown to indicate the
   * underlying cause.
   *
   * @throws  DirectoryException  If the TLS connection security provider could
   *                              not be enabled and the underlying connection
   *                              has been closed.
   */
  public void enableTLSConnectionSecurityProvider()
         throws DirectoryException
  {
    if (tlsSecurityProvider == null)
    {
      Message message = ERR_LDAP_TLS_NO_PROVIDER.get();
      disconnect(DisconnectReason.OTHER, false, message);
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message);
    }
    clearSecurityProvider = securityProvider;
    setConnectionSecurityProvider(tlsSecurityProvider);
  }
  /**
   * Disables the TLS connection security provider on this client connection.
   * This must also eliminate any authentication that had been performed on the
   * client connection so that it is in an anonymous state.  If a problem occurs
   * while attempting to revert the connection to a non-TLS-protected state,
   * then an exception must be thrown and the client connection must be
   * terminated.
   *
   * @throws  DirectoryException  If TLS protection cannot be reverted and the
   *                              underlying client connection has been closed.
   */
  public void disableTLSConnectionSecurityProvider()
         throws DirectoryException
  {
    Message message = ERR_LDAP_TLS_CLOSURE_NOT_ALLOWED.get();
    disconnect(DisconnectReason.OTHER, false, message);
    throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                 message);
  }
  /**
   * Sends a response to the client in the clear rather than through the
   * encrypted channel.  This should only be used when processing the StartTLS
   * extended operation to send the response in the clear after the TLS
   * negotiation has already been initiated.
   * encrypted channel. This should only be used when processing the
   * StartTLS extended operation to send the response in the clear after
   * the TLS negotiation has already been initiated.
   *
   * @param  operation  The operation for which to send the response in the
   *                    clear.
   *
   *
   * @throws  DirectoryException  If a problem occurs while sending the response
   *                              in the clear.
   * @param operation
   *          The operation for which to send the response in the clear.
   * @throws DirectoryException
   *           If a problem occurs while sending the response in the
   *           clear.
   */
  public void sendClearResponse(Operation operation)
         throws DirectoryException
      throws DirectoryException
  {
    if (clearSecurityProvider == null)
    {
      Message message = ERR_LDAP_NO_CLEAR_SECURITY_PROVIDER.get(toString());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message);
    }
    sendLDAPMessage(clearSecurityProvider,
                    operationToResponseLDAPMessage(operation));
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public DN getKeyManagerProviderDN()
  {
    return connectionHandler.getKeyManagerProviderDN();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public DN getTrustManagerProviderDN()
  {
    return connectionHandler.getTrustManagerProviderDN();
  }
  /**
   * Retrieves the alias of the server certificate that should be used
   * for operations requiring a server certificate.  The default
   * implementation returns {@code null} to indicate that any alias is
   * acceptable.
   *
   * @return  The alias of the server certificate that should be used
   *          for operations requring a server certificate, or
   *          {@code null} if any alias is acceptable.
   */
  @Override
  public String getCertificateAlias()
  {
    return connectionHandler.getSSLServerCertNickname();
    sendLDAPMessage(operationToResponseLDAPMessage(operation));
  }
  /**
   * Retrieves the length of time in milliseconds that this client
   * connection has been idle.
   * <BR><BR>
   * connection has been idle. <BR>
   * <BR>
   * Note that the default implementation will always return zero.
   * Subclasses associated with connection handlers should override
   * this method if they wish to provided idle time limit
   * functionality.
   * Subclasses associated with connection handlers should override this
   * method if they wish to provided idle time limit functionality.
   *
   * @return  The length of time in milliseconds that this client
   *          connection has been idle.
   * @return The length of time in milliseconds that this client
   *         connection has been idle.
   */
  @Override
  public long getIdleTime()
  {
    if (operationsInProgress.isEmpty() && getPersistentSearches().isEmpty())
    if (operationsInProgress.isEmpty()
        && getPersistentSearches().isEmpty())
    {
      return (TimeThread.getTime() - lastCompletionTime.get());
    }
@@ -2974,27 +2456,131 @@
    }
  }
  private void initializeOperationMonitors() {
    this.addMonitor = OperationMonitor.getOperationMonitor(
            ADD);
    this.searchMonitor = OperationMonitor.getOperationMonitor(
            SEARCH);
    this.abandonMonitor = OperationMonitor.getOperationMonitor(
            ABANDON);
    this.bindMonitor = OperationMonitor.getOperationMonitor(
            BIND);
    this.compareMonitor = OperationMonitor.getOperationMonitor(
            COMPARE);
    this.delMonitor = OperationMonitor.getOperationMonitor(
            DELETE);
    this.extendedMonitor = OperationMonitor.getOperationMonitor(
            EXTENDED);
    this.modMonitor = OperationMonitor.getOperationMonitor(
            MODIFY);
    this.moddnMonitor = OperationMonitor.getOperationMonitor(
            MODIFY_DN);
    this.unbindMonitor = OperationMonitor.getOperationMonitor(
            UNBIND);
  /**
   * Set the connection provider that is not in use yet. Used in TLS
   * negotiation when a clear response is needed before the connection
   * provider is active.
   *
   * @param provider
   *          The provider that needs to be activated.
   */
  public void setTLSPendingProvider(ConnectionSecurityProvider provider)
  {
    tlsPendingProvider = provider;
  }
  /**
   * Set the connection provider that is not in use. Used in SASL
   * negotiation when a clear response is needed before the connection
   * provider is active.
   *
   * @param provider
   *          The provider that needs to be activated.
   */
  public void setSASLPendingProvider(ConnectionSecurityProvider provider)
  {
    saslPendingProvider = provider;
  }
  /**
   * Enable the provider that is inactive.
   */
  public void enableTLS()
  {
    this.asn1Reader =
        ASN1.getReader(saslChannel, tlsPendingProvider.getAppBufSize(),
            connectionHandler.getMaxRequestSize());
    activeProvider = tlsPendingProvider;
    tlsChannel.redirect(tlsPendingProvider);
    tlsPendingProvider = null;
  }
  /**
   * Set the security provider to the specified provider.
   *
   * @param sslProvider
   *          The provider to set the security provider to.
   */
  public void enableSSL(ConnectionSecurityProvider sslProvider)
  {
    this.asn1Reader =
        ASN1.getReader(saslChannel, sslProvider.getAppBufSize(),
            connectionHandler.getMaxRequestSize());
    activeProvider = sslProvider;
    tlsChannel.redirect(sslProvider);
  }
  /**
   * Enable the SASL provider that is currently inactive or pending.
   */
  public void enableSASL()
  {
    activeProvider = saslPendingProvider;
    saslChannel.redirect(saslPendingProvider);
    asn1Reader =
        ASN1.getReader(saslChannel,
            saslPendingProvider.getAppBufSize(), connectionHandler
                .getMaxRequestSize());
    saslPendingProvider = null;
  }
  /**
   * Return the certificate chain array associated with a connection.
   *
   * @return The array of certificates associated with a connection.
   */
  public Certificate[] getClientCertificateChain()
  {
    if (activeProvider != null)
    {
      return activeProvider.getClientCertificateChain();
    }
    else
      return new Certificate[0];
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public int getSSF()
  {
    if (activeProvider != null)
      return activeProvider.getSSF();
    else
      return 0;
  }
  private void initializeOperationMonitors()
  {
    this.addMonitor = OperationMonitor.getOperationMonitor(ADD);
    this.searchMonitor = OperationMonitor.getOperationMonitor(SEARCH);
    this.abandonMonitor = OperationMonitor.getOperationMonitor(ABANDON);
    this.bindMonitor = OperationMonitor.getOperationMonitor(BIND);
    this.compareMonitor = OperationMonitor.getOperationMonitor(COMPARE);
    this.delMonitor = OperationMonitor.getOperationMonitor(DELETE);
    this.extendedMonitor =
        OperationMonitor.getOperationMonitor(EXTENDED);
    this.modMonitor = OperationMonitor.getOperationMonitor(MODIFY);
    this.moddnMonitor = OperationMonitor.getOperationMonitor(MODIFY_DN);
    this.unbindMonitor = OperationMonitor.getOperationMonitor(UNBIND);
  }
}